js异步

学习js开发,无论是前端开发还是node.js,都避免不了要接触异步编程这个问题,就和其它大多数以多线程同步为主的编程语言不同,js的主要设计是单线程异步模型。正因为js天生的与众不同,才使得它拥有一种独特的魅力,也给学习者带来了很多探索的道路。本文就从js的最初设计开始,整理一下js异步编程的发展历程。

什么是异步

在研究js异步之前,先弄清楚异步是什么。异步是和同步相对的概念,同步,指的是一个调用发起后要等待结果返回,返回时候必须拿到返回结果。而异步的调用,发起之后直接返回,返回的时候还没有结果,也不用等待结果,而调用结果是产生结果后通过被调用者通知调用者来传递的。

举个例子,A想找C,但是不知道C的电话号码,但是他有B的电话号码,于是A给B打电话询问C的电话号码,B需要查找才能知道C的电话号码,之后会出现两种场景看下面两个场景:

A不挂电话,等到B找到号码之后直接告诉A

A挂电话,B找到后再给A打电话告诉A

能感受到这两种情况是不同的吧,前一种就是同步,后一种就是异步。

为什么是异步的

先来看js的诞生,JavaScript诞生于1995年,由Brendan Eich设计,最早是在Netscape公司的浏览器上实现,用来实现在浏览器中处理简单的表单验证等用户交互。至于后来提交到ECMA,形成规范,种种历史不是这篇文章的重点,提到这些就是想说一点,js的最初设计就是为了浏览器的GUI交互。对于图形化界面处理,引入多线程势必会带来各种各样的同步问题,因此浏览器中的js被设计成单线程,还是很容易理解的。但是单线程有一个问题:一旦这个唯一的线程被阻塞就没办法工作了--这肯定是不行的。由于异步编程可以实现“非阻塞”的调用效果,引入异步编程自然就是顺理成章的事情了。

现在,js的运行环境不限于浏览器,还有node.js,node.js设计的最初想法就是设计一个完全由事件驱动,非阻塞式IO实现的服务器运行环境,因为网络IO请求是一个非常大的性能瓶颈,前期使用其他编程语言都失败了,就是因为人们固有的同步编程思想,人们更倾向于使用同步设计的API。而js由于最初设计就是全异步的,人们不会有很多不适应,加上V8高性能引擎的出现,才造就了node.js技术的产生。node.js擅长处理IO密集型业务,就得益于事件驱动,非阻塞IO的设计,而这一切都与异步编程密不可分。

线程和进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源

一个进程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  1. 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

  2. 线程的划分尺度小于进程,使得多线程程序的并发性高。

  3. 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

  4. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

  5. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

引入同步和异步

Javascript语言的执行环境是"单线程"(single thread,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推)。

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

同步模式" 就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

异步模式" 非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

js异步原理

栈 堆 队列 事件循环

这个队列就是异步队列,它是处理异步事件的核心,整个js调用时候,同步任务和其他编程语言一样,在栈中调用,一旦遇上异步任务,不立刻执行,直接把它放到异步队列里面,这样就形成了两种不同的任务。由于主线程中没有阻塞,很快就完成,栈中任务边空之后,就会有一个事件循环,把队列里面的任务一个一个取出来执行。只要主线程空闲,异步队列有任务,事件循环就会从队列中取出任务执行。

说的比较简单,js执行引擎设计比这复杂的多得多,但是在js的异步实现原理中,事件循环和异步队列是核心的内容。

异步编程实现

回调函数(callback)

A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.

翻译:回调是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。( 也即:B函数被作为参数传递到A函数里,在A函数执行完后再执行B )


callback方式利用了函数式编程的特点,把要执行的函数作为参数传入,由被调用者控制执行时机,确保能够拿到正确的结果。这种方式初看可能会有点难懂,但是熟悉函数式编程其实很简单,很好地解决了最基本的异步问题,早期异步编程只能通过这种方式。

然而这种方式会有一个致命的问题,在实际开发中,模型总不会这样简单,下面的场景是常有的事:

fun1(data => {
// ...
fun2(data, result => {
// ...
fun3(result, () => {
// ...
});
});
});

整个随着系统越来越复杂,整个回调函数的层次会逐渐加深,里面再加上复杂的逻辑,代码编写维护都将变得十分困难,可读性几乎没有。这被称为毁掉地狱,一度困扰着开发者,甚至是曾经异步编程最为人诟病的地方

promise

使用回调函数来编程很简单,但是回调地狱实在是太可怕了,嵌套层级足够深之后绝对是维护的噩梦,而promise的出现就是解决这一问题的。promise是按照规范实现的一个对象,ES6提供了原生的实现,早期的三方实现也有很多。在此不会去讨论promise规范和实现原理,重点来看promise是如何解决异步编程的问题的。

Promise对象代表一个未完成、但预计将来会完成的操作,有三种状态:

pending:初始值,不是fulfilled,也不是rejected

resolved(也叫fulfilled):代表操作成功

rejected:代表操作失败

整个promise的状态只支持两种转换:从pending转变为resolved,或从pending转变为rejected,一旦转化发生就会保持这种状态,不可以再发生变化,状态发生变化后会触发then方法。这里比较抽象,我们直接来改造上面的例子:

		new Promise((reso, rej) => {
setTimeout(() => {
reso('hello')
}, 2000)
}).then(res => {
console.log(res);
return new Promise(reso => {
setTimeout(() => {
reso('world')
}, 2000)
})
}).then(res => {
console.log(res);
})

Promise是一个构造函数,它创建一个promise对象,接收一个回调函数作为参数,而回调函数又接收两个函数做参数,分别代表promise的两种状态转化。resolve回调会使promise由pending转变为resolved,而reject 回调会使promise由pending转变为rejected。

当promise变为resolved时候,then方法就会被触发,在里面可以获取到resolve的内容,then方法。而一旦promise变为rejected,就会产生一个error。无论是resolve还是reject,都会返回一个新的Promise实例,返回值将作为参数传入这个新Promise的resolve函数,这样就可以实现链式调用,对于错误的处理,系统提供了catch方法,错误会一直向后传递,总是能被下一个catch捕获。用promise可以有效地避免回调嵌套的问题,代码会变成下面的样子:

fun1().then(data => {
// ...
return fun2(data);
}).then(result => {
// ...
return fun3(result);
}).then(() => {
// ...
});

generator

async,await

js-同步和异步的更多相关文章

  1. js同步、异步、回调的执行顺序以及闭包的理解

    首先,记住同步第一.异步第二.回调最末的口诀 公式表达:同步=>异步=>回调 看一道经典的面试题: for (var i = 0; i < 5; i++) { setTimeout( ...

  2. JS同步与异步;

    JS的同步与异步 同步:代买从上往下的执行 异步:每个模块各执行各的,同时执行,互不干扰 四个异步事件:(1)定时器(2)ajax(3)时间的绑定(4)回调函数 现在用定时器来说一说setTimeOu ...

  3. js同步、异步、延时、无阻塞加载

    一.同步加载 平常默认用的都是同步加载.如:<script src="http://yourdomain.com/script.js"></script> ...

  4. js同步和异步

    JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事. JavaScript的单线程,与它的用途有关.作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作 ...

  5. 【Mocha.js 101】同步、异步与 Promise

    前情提要 在上一篇文章<[Mocha.js 101]Mocha 入门指南>中,我们提到了如何用 Mocha.js 进行前端自动化测试,并做了几个简单的例子来体验 Mocha.js 给我们带 ...

  6. 关于js中的同步和异步

    最近看到前端面试问到js中的同步和异步,这个问题该怎么回答? 梳理一下,js对于异步的处理,很多人的第一反应是ajax,这只能说是对了一半. 1.个人觉得,js中,最基础的异步是setTimeout和 ...

  7. js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)

    javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步. javascript事件循环 事件循环 ...

  8. js回调函数以及同步与异步

    1. 背景介绍javascript的单线程特性由于javascript语言是一门“单线程”的语言,所以,javascript就像一条流水线,仅仅是一条流水线而已,要么加工,要么包装,不能同时进行多个任 ...

  9. JS中的同步和异步

    javascript语言是一门“单线程”的语言,不像java语言,类继承Thread再来个thread.start就可以开辟一个线程,所以,javascript就像一条流水线,仅仅是一条流水线而已,要 ...

  10. js中的异步与同步,解决由异步引起的问题

    之前在项目中遇到过好多次因为异步引起的变量没有值,所以意识到了认识js中同步与异步机制的重要性 在单线程的js中,异步代码会被放入一个事件队列,等到所有其他代码执行后再执行,而不会阻塞线程. 下面是j ...

随机推荐

  1. MyBatis 进阶,MyBatis-Plus!(基于 Springboot 演示)

    这一篇从一个入门的基本体验介绍,再到对于 CRUD 的一个详细介绍,在介绍过程中将涉及到的一些问题,例如逐渐策略,自动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没提及到,具体可以参考官 ...

  2. 如何安装eclipse

    1.打开浏览器输入网址:http://www.eclipse.org 进入官方 2.(目前我使用windows操作系统),下拉界面选择"windows"后的"64-bit ...

  3. 硬核测试:Pulsar 与 Kafka 在金融场景下的性能分析

    背景 Apache Pulsar 是下一代分布式消息流平台,采用计算存储分层架构,具备多租户.高一致.高性能.百万 topic.数据平滑迁移等诸多优势.越来越多的企业正在使用 Pulsar 或者尝试将 ...

  4. 056 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 03 一维数组的应用

    056 01 Android 零基础入门 01 Java基础语法 06 Java一维数组 03 一维数组的应用 本文知识点:数组的实际应用 程序开发中如何应用数组? 程序代码及其运行结果: 不同数据类 ...

  5. 【题解】X龙珠

    明天好像要考链表今晚笔者来了解下. 题目链接 解: 对于这道题,由于前面要与后面重新连起来,于是我们考虑链表. 我们先正常用链表维护关系.然后,我们从大到小枚举. 对于这个数,如果它后面有数(因为是一 ...

  6. 让我们创建屏幕- Android UI布局和控件

    下载LifeCycleTest.zip - 278.9 KB 下载ViewAndLayoutLessons_-_Base.zip - 1.2 MB 下载ViewAndLayoutLessons_-_C ...

  7. 启动VNC Shell扩展

    下载source files - 18.3 Kb Introduction 我们使用RealVNC来远程控制我们的网络中的pc机,VNC是一个伟大的产品,但如果不记住计算机名称,它可以是乏味的,在网络 ...

  8. JS学习之路一

    1.准备 ①安装vscode 地址:https://vscode.en.softonic.com/ ②安装node.js node -v npm -v 地址:https://nodejs.org/zh ...

  9. 租房数据分析,knn算法使用

    import numpy as np import pandas as pd import matplotlib.pyplot as plt data = pd.read_excel('jiemo.x ...

  10. 提取swagger内容到csv表格,excel可打开

    swagger生成的页面api接口统计,有几种方法 直接在前端用js提取出来,较麻烦(不推荐,不同版本的页面生成的标签有可能不一样,因此可能提取不出来) //apilet a = document.g ...