“如果你担心某种情况发生,那么它就更有可能发生。” —— 墨菲定律
我们都知道async
、await
其实是promise
的语法糖,在过去还没有generator
和async\await
时,只使用promise
处理异步问题很容易出现多层回调嵌套的情景,比如我们第二个异步操作依赖于第一个异步请求返回的数据,那我们就需要在resolve
后从第一个then
的对应回调方法中去传递这个值,同理要是之后还有对前面操作的依赖就会不断嵌套下去…
回调地狱
举个例子:假设有一个场景,我们需要先根据用户的申请编号(applyNo)去影像系统拿对应的影像编号(imageNo)再根据这个影像编号去请求影像信息。使用promise
来实现大概有如下的代码:
1 | fetch('获取影像编号接口', { |
Async和Await带来了什么
有了async
和await
后,我们是如何实现上述的逻辑呢?
1 | async function fetchDocInfo(applyNo) { |
可以看到,通过这种方式实现,首先嵌套的问题没了,其次它更符合我们思考问题的流程,使我们能够以编写同步代码的方式去进行异步编程;这也是我个人看来async/await
对开发体验而言带来的最大提升。
Async做了什么
带async的函数最终会返回一个Promise对象,即使你在函数中返回的是一个普通变量,它也会通过Promise.resolve()
封装后再返回。其次在我们coding的过程中,await
需要写在async
函数内部,否则会报错。
Await又做了什么
await
做了一件事,等!它会等它右侧表达式的结果。而且还要区分结果的类型!当表达式结果是Promise
时,它会进入异步的等待流程,直到Promise
被resolve
,最后将resolve
的值作为await
的等待结果;如果表达式结果就是一个直接量,那这个结果就是await
要等的值。
Async/Await的执行时机
之前的博客有聊过EL的一些执行输出场景,现在就可以把缺少的async/await
加进去一起讨论了,首先第一点是我个人对比了几个网络上常见的执行DEMO得出的结论:async
函数内部在到达await
表达式前,可以等价于Promise
内的构造函数部分,即这块区域的代码是同步执行的。了解这点后,综合前文讨论的根据await
等待的表达式结果类型判断即可正确得到我们的执行输出顺序。上个DEMO:
1 | async function async1() { |
最终输出结果是1 3 4 2
,为啥呢?首先调用栈先调用了async1()
,然后内部先输出同步的1,然后调用async2
,async2
返回一个promise
同时它内部的输出一样是同步输出,再加外层的4
,就是1 3 4
的顺序,最后async2
返回的promiseresolved
了,await
等待结束,输出2
。
我自己在这里也改写了一个DEMO,来看看是否真得理解了:
1 | async function async1() { |
还是一步步看,调用栈执行async1()
,然后同步输出1
,然后async1
内的第二个输出结果需要await
等async2
的返回值,然后执行async2
,同步代码,输出3
,由于async2
最终返回的是一个resolved
的Promise对象,还是一个异步的状态进入micro task
队列维护,我们会继续执行我们的同步任务,即最外层的5
,之后Promise.resolve
传入的值返回被await
等到,输出4
,这里等待结束,继续输出之后的2
,所以有最终结果1 3 5 4 2
。
如果你看到这里了,可能已经大概摸得差不多了,那我再把上面这个DEMO的async2
返回变为直接量会如何呢?
1 | async function async1() { |
最终输出结果依旧是1 3 5 4 2
。
综上,我们可以得到:await
其实就是一个异步等待结果的过程,得到结果才会resolved
从而执行后续代码,同时我们可以把整个async
函数视作一个Promise的执行流程,在内部await
前代码块等价于Promise
构造中的同步代码块,在await
后的代码可以理解为then
方法中对应resolved
的回调处理部分,当Promise
被resolved
后就会被回调。还是那句话,同步优先。
真的盘清楚了?那你的Promise基础够硬么?如果我将Promise.resolve
改成Promise.reject
呢?
1 | async function async1() { |
最后输出1 3 5
…嗯?没了?
可以看到控制台仅输出1 3 5
,并且有一个未捕获的Promise值,这是因为我们并没有catch
获取这个值,await
也无法接收这个值,自然无法输出4
,而之后的2
是resolved
的回调而不是rejected
的,自然也莫得~
最后的最后,我们看一看某条的一道看烂的题:
1 | async function async1() { |
①同步输出script start
;
②setTimeout
进入宏任务队列;
③调用async1
,同步输出async1 start
;
④await
等待async2
被resolved
的结果返回;
⑤执行async2
,同步输出async2
;
⑥此时await
还处于异步等待环节,await
之后的等价于then
中对应resolved
的回调,进入微任务队列维护,然后继续处理优先级更高的同步问题;
⑦Promise的构造函数中同步输出promise1
;
⑧resolve
后then
回调放入微任务队列维护,此时微任务队列中有async1 end
和promise2
;
⑨继续执行调用栈中的同步任务,输出script end
;
⑩此时同步任务已经全部跑完,我们回头看异步队列中维护的任务,由于微任务优先级高于宏任务,所以我们有async1 end
,promise2
,setTimeout
的输出顺序;
那最后输出是否是上面说的这样呢?
成了!