本文参考:《js中的同步和异步》、《大白话讲解Promise(一)》、《async 函数的含义和用法》,感谢!

代码测试使用菜鸟教程:《菜鸟教程在线编辑器

一、理解JavaScript、sync和async

JavaScript是一门单线程的语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。

因为JavaScript的单线程,因此同个时间只能处理同个任务,所有任务都需要排队,前一个任务执行完,才能继续执行下一个任务。但是,如果前一个任务的执行时间很长,比如文件的读取操作或ajax操作,后一个任务就不得不等着,拿ajax来说,当用户向后台获取大量的数据时,不得不等到所有数据都获取完毕才能进行下一步操作,用户只能在那里干等着,严重影响用户体验。因此,JavaScript在设计的时候,就已经考虑到这个问题,主线程可以完全不用等待文件的读取完毕或ajax的加载成功,可以先挂起处于等待中的任务,先运行排在后面的任务,等到文件的读取或ajax有了结果后,再回过头执行挂起的任务,因此,任务就可以分为同步任务和异步任务。

sync(同步)任务:主线程上排队执行的任务。只有前一个任务执行完毕,才能继续执行下一个任务。(按代码书写的先后顺序执行,上一行代码执行完成才会执行下一行代码)

async(异步)任务:不进入主线程,而进入任务队列的任务。只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。(跟代码执行时间的长短有关,所需时间短的先执行完)

JavaScript的异步机制包括以下几个步骤:

(1)所有同步任务都在主线程上执行,行成一个执行栈
(2)主线程之外,还存在一个任务队列,只要异步任务有了结果,就会在任务队列中放置一个事件
(3)一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面还有哪些事件,哪些对应的异步任务,于是异步任务结束等待状态,进入执行栈,开始执行
(4)主线程不断的重复上面的第三步

setTimeout是一个延迟函数,可以用它来模拟ajax请求,它是一个异步任务,不会进入主线程,而是进入任务队列,任务队列是一个先进先出的数据结构,第一个setTimeout延迟20ms,第二个setTimeout延迟10ms,所以第二个setTimeout会先执行,第一个setTimeout后执行。fun1()和fun3()位于主线程上,会顺序执行,所以上面的执行顺序是fun1()、fun3()、fun4()、fun2(),依次输出1,3,4,2。

二、异步编程方法

(1)回调函数

一般将回调函数提出来单独定义,更便于理解,如下:

下面,将回调函数在主函数内部的位置放到console.log('这里是主函数');上面,则运行结果如下:

(2)Promise

先在控制台看一下Promise这个构造函数都有哪些方法:

1、先创建一个Promise,Promise的构造函数接收一个参数,这个参数是一个函数。这个参数函数接收两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数,按照标准来讲,resolve是将Promise的状态置为fulfilled(完成),reject是将Promise的状态置为rejected(拒绝)。

这里return出Promise对象,也就是说,执行这个函数我们得到了一个Promise对象,但是我们并没有定义回调函数resolve的内部逻辑,因此不会调用回调函数resolve,因为无从调用。

2、那么,我们怎么样使用回调函数resolve()和reject(),这就要用到Promise对象上的then、catch方法了。

仔细一看,这个逻辑和上面讲的回调函数一样嘛!是的,实际上就是一样的逻辑。而Promise的优势在于多层回调,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。

3、Promise链式操作的用法

从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

下一个then接收的是上一个then返回的Promise,在then方法中,也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了,比如我们把上面的代码修改成这样:

4、reject的用法:reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。如果使用了reject回调函数,则必须添加then方法的第二个参数,或者添加catch方法,否则会报错

5、catch的用法:它和then的第二个参数一样,用来指定reject的回调。效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。

在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到如下图的结果。

也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

catch一般写在重要或者容易出错的then后面,catch捕捉其上的then中报的错误,一旦其上面的某个then有错,就会进到catch中,但是就如同try/catch语句一样,并不会影响catch后面语句的运行。

6、all的用法:all是全部的意思,all方法的效果实际上是「谁跑的慢,以谁为准执行回调」。all()接收数组作为参数,其回调函数返回的结果也是数组,即console.log(results);返回的是下图红框中的数组。具体可以看看下图的示例,红框中标注的setTimeou函数的延迟时间会影响各函数的执行顺序,但不会影响最终输出的数组中的元素顺序,结果数组的元素顺序同输入数组的元素顺序是一致的。

7、race的用法:race本身是赛跑的意思,效果实际上是「谁跑的快,以谁为准执行回调」。race()接收数组作为参数,console.log(results);返回的是下图红框中的“随便什么数据1”。具体可以看看下图的示例,红框中标注的setTimeou函数的延迟时间会影响各函数的执行顺序,同时输入数组中元素的先后顺序也会影响各函数执行的先后顺序(延迟时间一致时,数组中元素在前者先执行),因为存在这两个先后顺序,而赛跑只有一个第一名,所以race()最终只会返回一个回调函数的执行结果。

(3)async、await用法

1、很多人认为async 函数是Promise的升级版,异步操作的终极解决方案。

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

await是在等待一个Promise的异步返回。用await声明的Promise异步返回,必须等到触发的异步操作完成,有返回值的时候,才会接着执行函数体内后面的语句。

2、我们可以将上面的示例改写一下,使用 then 方法添加回调函数,在回调函数中获取resolve()返回的数据。可以看到,then的执行总是最后的。

3、上面提到的Promise链式操作的用法,也可以使用async/await方法改写:

4、去掉async/await对比一下结果,可以发现有async/await时,整个demo内是同步(按书写的先后顺序,可以人为控制谁先执行谁后执行)进行的,去掉则变为异步(按所需时间长短,比如请求后台数据时我们无法人为控制请求时间的长短,如果第二个请求需要使用第一个请求返回的数据,我们必须使用async/await,否则无法有效获取到第一个请求返回的数据)。

5、await 后面的 Promise 对象,运行结果可能是 rejected,如何不捕捉的话,会报错,我们可以通过then的第二个参数或者Promise的catch方法捕捉错误。

6、使用demo.then方法调用resolve回调函数获取输出结果时,我们不将catch放在await后面的Promise上,因为catch捕捉到错误后会继续向下执行,又因为catch已经捕捉了错误,就没有错误传递下去了,所以在之后的then中就会调动resolve回调函数,返回undefined,而这是不正确的。

7、注意事项:

await 命令只能用在 async 函数之中,如果用在普通函数,就会报错

forEach是不支持通过改写为async/await函数实现同步操作的,想要使用循环达到同步效果,请使用for循环。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐