“如果你担心某种情况发生,那么它就更有可能发生。” —— 墨菲定律
我们都知道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的输出顺序;
那最后输出是否是上面说的这样呢?

成了!
