当 await 关键字与异步函数一起使用时,它的真正优势就变得明显了 —— 事实上, await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了。 -- 摘自MDN
题目
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('illegalscript start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('illegalscript end');
题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及JS的事件循环的相关问题。我们知道,这无疑是涉及到的微任务与宏任务的执行顺序,我们不妨设两个队列: 微任务队列microQue、宏任务队列:macroQue来存放对应输出的值
题解过程
首先, 两队列为空:
microQue:[]
macroQue: []
代码从上而下执行依次:
首先定义两个异步函数
async1,async2执行
console.log('illegalscript start');首先输出 :
"illegalscript start"当前状态
已输出:
"illegalscript start"
微任务队列: []
宏任务队列: []执行:
setTimeout(function() { console.log('setTimeout'); }, 0);setTimeout推进一个宏任务输出:console.log('setTimeout');当前状态:
已输出:
"illegalscript start"
微任务队列: []
宏任务队列: ["console.log('setTimeout')"]执行
async1()
立即输出:"async1 start", 随后执行 await async2()
这里是整个题目最容易出错的地方,要认真审题:
回顾文章开头的引用:
await 只在异步函数里面才起作用。它可以放在任何异步的,基于 promise 的函数之前。它会暂停代码在该行上,直到 promise 完成,然后返回结果值。在暂停的同时,其他正在等待执行的代码就有机会执行了
await的含义为等待,也就是 async 函数需要等待await后的函数执行完成并且有了返回结果(Promise对象)之后,才能继续执行下面的代码。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。 说那么多怕是要晕了,其实很简单,看例子就明白:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
等价于
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}
好,看回题目,此执行阶段,立即输出:"async2"
执行至此,async1函数里面剩余代码暂停执行直到 promise 完成,即剩余代码添加至微任务队列中去。此时正在嗷嗷待哺的正在等待执行的代码有了机会,继续往下执行。
当前状态:
已输出:
"illegalscript start","async1 start","async2"
微任务队列: ["console.log('async1 end')"]
宏任务队列: ["console.log('setTimeout')"]
执行:
new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); });
立即输出:promise1
执行resolve(),then里面内容推进微任务队列
当前状态:
已输出:
"illegalscript start","async1 start","async2""promise1"
微任务队列: ["console.log('async1 end')", "console.log('promise2')"]
宏任务队列: ["console.log('setTimeout')"]
执行:
console.log('illegalscript end');立即输出:
"illegalscript end"已输出:
"illegalscript start","async1 start","async2""promise1""illegalscript end"
微任务队列: ["console.log('async1 end')", "console.log('promise2')"]
宏任务队列: ["console.log('setTimeout')"]本轮事件循环已结束,开始执行并清空微任务队列:
依次输出:"async1 end"、"promise2"当前状态
已输出:
"illegalscript start","async1 start","async2""promise1""illegalscript end""async1 end""promise2"
微任务队列: ["console.log('async1 end')", "console.log('promise2')"]
宏任务队列: ["console.log('setTimeout')"]进入下一事件循环,执行清空宏任务队列 输出: setTimeout
当前状态:
已输出:
"illegalscript start","async1 start","async2""promise1""illegalscript end""async1 end""promise2""setTimeout"
微任务队列: []
宏任务队列: []
至此,代码已执行完毕,微任务队列以及宏任务队列也已清空,执行结束。 所以最终输出结果先后顺序为:
- illegalscript start
- async1 start
- async2
- promise1
- illegalscript end
- async1 end
- promise2
- setTimeout
如图:
本文使用 mdnice 排版