await探究
这周在js群里讨论了一道今日头条的面试题,这题考察的是对microtask(微任务)和micratask(宏任务)的理解。题目和输出如下:
1 | async function async1() { |
这里就不对微任务和宏任务进行深入探究了,这里我们来探讨一个有意思的小问题(假设你已经掌握了微任务和宏任务)。
很多人对于async1 end出现在script end之后,而不是出现在async2之后感到有所疑惑。
出现这个疑惑是因为对await的工作原理不够了解,下面我们来解释一下。
先说说为什么有的人会认为async1 end出现在script end之后。这是因为,在代码的书写上,由于使用了await关键字,所以可以使得我们用同步的代码去写异步的代码。
1 | await async2(); |
很多人会觉得是js引擎在执行第一句的时候会暂停,等异步函数执行完了再去执行第二句,这就是误区所在。async/await的作用只是让我们像写同步代码一样写异步代码,而不是把异步代码变成同步代码。 这段代码还是异步的。
await只是promise的一个语法糖,我们对async1进行一个改写,你就能明白await的运行机制了。
1 | async function async1() { |
试着运行改写后的整段代码,是不是发现运行结果是一样的?
我们在来假设一下async2会返回参数,那么代码改写前后的效果如下:
1 | async function async1() { |
我想看到这里你就能明白了吧,这也就解释了为什么通过
1 | let msg = await async2(); |
这样的写法,我们拿到promise拿到的结果。其实,我们并不是拿到了结果,而是把结果传给了msg这个形参。这放映了一个有意思的问题,代码的执行很可能和我们感觉的非常不一样。
我们在来看看上面的改写,不知道你有没有发现什么问题?
你应该一经发现了,对于promise而言,居然没有catch。很遗憾,这个就是使用await带来的代价。为了应对处理,我们需要使用try…catch来额外的捕获异常。目前还没有更好的解决方案,如果有请你告诉我,博客下面有我的邮箱。
对错误的处理,从另一个角度也说明了在异步嵌套较少情况下(一两层)使用async/await未必是最理想的选择。当然,这还的看业务完整逻辑来判断,比如我们请求一个借口,在封装好的接口内如果已经对错误进行了统一的预处理,那么之后使用await则不用再考虑这个问题。
最后在说一个小小问题,当我们把最开始的代码块放在浏览器里运行的时候,在promise2之后,setTimeout之前会输出一个undefined。这是因为我们通过console输入了一段代码段,而这段代码段会成为当前javascript主线程的执行脚本,在这段脚本执行完后(微任务也执行完才算),会有一个输出值给到控制台,这个输出是脚本最后一条语句/表达式的值。我们这里最后一句是:
1 | console.log('script end'); |
而它执行值正是undefined。