为什么要写这篇文章?
因为我发现在做关于异步的面试题,遇到 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"))