这是一道涉及事件循环的笔试题:
题目:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start
// async1 start
// async2
// promise1
// script end'
// async1 end
// promise2
// setTimeout
在分析这道题前先说说几个知识点:
任务队列
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理的一个任务队列
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
宏任务
(macro)task(又称为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务。
浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束之后,在下一个(macro)task执行开始前,对页面进行重新渲染:
(macro)task -> 渲染 -> (macro) task ...
(macro)task主要包括:
- script(整体代码)
- setTimeout
- setInterval
- I/O
- UI事件
- postMessage
- MessageChannel
- setImmediate(Node.js环境)
微任务
microtask(又称为微任务),可以理解是在当前task执行结束后立即执行的任务。也就是在当前task任务后,在下一个task之前,在渲染之前。
在某个宏任务执行之后,就会将在它执行期间产生的所有微任务都执行完毕。
microtask主要包括:
- Promise.then
- MutaionObserver
- process.nextTick(Node.js环境)
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
流程图如下:
Promise和aysnc中的立即执行
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。
在async/await中,在出现await之前,其中的代码也是立即执行,在出现await时发生了什么?
await是一个让出线程的标志。await后面紧跟着的表达式会先执行,将接着await下面的代码加到micromask中,然后就会跳出整个 async函数执行后面的代码。
由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。所以对于本题中的:
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
等价于:
async function async1() {
console.log('async1 start');
Promise.reselve(async2().then(() => {
console.log('async1 end');
}))
}
回到本题
了解上面的知识点后,对于本题就很简单了:
-
首先进入第一个宏任务(script整体代码),遇到同步任务直接执行,即输出 ‘script start’
-
遇到setTimeout将其放到宏任务队列
-
遇到async1(),将async1函数里面的同步任务直接执行,输出‘async1 start’,然后将await紧跟着的表达式执行,执行async2(),输出‘async2’,最后将await后面的代码加入到微任务队列,跳出函数async1,执行下面的代码
-
遇到Promise,将里面的同步任务立即执行,输出‘promise1’,将then后面的代码加入到微任务队列.
-
最后立即执行console.log(‘script end’),输出‘script end’。
-
到此,第一个宏任务结束,紧接着执行这一轮的微任务,依次执行微任务队列,输出‘async1 end’,再输出‘promise2’
-
微任务执行完毕,接着执行下一个宏任务,这里只有setTimeout,执行它,输出‘setTimeout’
-
结束流程
变式一
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { //async2做出如下更改: new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise3'); resolve(); }).then(function() { console.log('promise4'); }); console.log('script end'); // script start // async1 start // promise1 // promise3 // script end // promise2 // async1 end // promise4 // setTimeout
变式二
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function() {
console.log(' setTimeout1') // 第三个宏任务
},0)
}
async function async2() {
//更改如下:
setTimeout(function() {
console.log('setTimeout2')
},0) // 第二个宏任务
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout3');
}, 0) // 第一个宏任务
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start
// async1 start
// promise1
// script end
// promise2
// setTimeout3
// setTimeout2
// setTimeout1
变式三
async function a1 () {
console.log('a1 start');
await a2();
console.log('a1 end');// 第二个微任务
}
async function a2 () {
console.log('a2');
}
console.log('script start');
setTimeout(() => {
console.log('setTimeout')
}, 0) // 第一个宏任务
Promise.resolve().then(() => {
console.log('promise1');
}) // 第一个微任务
a1();
let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})
promise2.then((res) => {
console.log(res)// 第三个微任务
Promise.resolve().then(() => {
console.log('promise3')//第4个微任务
})
})
console.log('script end')
// script start
// a1 start
// a2
// promise2
// script end
// promise1
// a1 end
// promise2.then
// promise3
// setTimeout