为什么要写这篇文章?
因为我发现在做关于异步的面试题,遇到 async/await 就老是做错,明明我已经很清楚的记得那些是宏任务、那些是微任务。也记得 event loop的执行顺序
-
一开始整个脚本作为一个宏任务执行
-
执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
-
当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
-
执行浏览器UI线程的渲染工作
-
检查是否有
Web Worker任务,有则执行 -
执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空
微任务包括: MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。
宏任务包括:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。
下面我们以一个 Promise 的题目展开
- 第一题:
const promise = new Promise((resolve, reject) => { //1
console.log(1); // 2
resolve('success') // 3
console.log(2); // 4
});
promise.then(() => {
console.log(3); // 5
});
console.log(4); // 6
解析:
- 第一行 new Promise (executor ) , 其中的 executor 会被立刻执行
- 第二行 输出 1
- 第三行 改变 promise 的
PromiseState:fulfilled,PromiseResult:success - 第四行 输出 2
- 第五行 此时 promise 的
PromiseState已经为fulfilled,把console.log(3)放入微任务列表,执行 - 第六行 输出 4
- 此时当前宏任务(源于脚本)执行完成,检查微任务列表,执行
console.log(3) - 输出 3
最初的 “pending” promise 相反,一个 resolved 或 rejected 的 promise 都会被称为 “settled”。 在第三步中,如果 new promise 的 executor 没有 resolve 或者 reject 函数,那么将一直不会执行第五步,把
console.log(3)放入微任务列表
- 第二题:
function fn1() {
console.log("1"); //1
new Promise(resolve => {
console.log("2") // 2
resolve() // 3
}).then(res => console.log("3")) // 4
}
fn1(); //5
console.log("4") //6
解析:
- 代码执行直接跳到第5行, 执行 fn1() 函数
- 第一行 输出 1
- 第二行 输出 2
- 第三行 改变 promise 的
PromiseState:fulfilled,PromiseResult:undefined - 第四行 此时 promise 的
PromiseState已经为fulfilled,把console.log(3)放入微任务列表,执行 - 第六行 输出 4
- 最后执行微任务队列中的
console.log(3)输出 3
答案: 1 2 4 3
那么我们如何把第二题变成 async/await 的形式呢?
async function fn1() {
console.log("1");
await async2();
console.log("3");
}
async function async2() {
console.log("2");
}
async1();
console.log("4")
上面转换后的代码和第二题的输出完全一样
结论: await 是重新把一个 new 了 PromiseState 为 fulfilled 的 promise ,然后再把 await 后面的代码放入这个 promise.then() 中 ,这也是为什么 2 会在 4 之前输出
new Promise(resolve=>{console.log("2"); resolve()}),
这里其实也可以直接转变为
Promise.resolve(console.log("2"))