JavaScript事件循环中微任务队列和宏任务队列的工作原理
- 宏任务队列(Macro - Task Queue):
- 宏任务是由宿主环境提供的异步任务,常见的宏任务有
setTimeout
、setInterval
、setImmediate
(仅在Node.js环境中有)、I/O
操作、UI渲染
等。
- 事件循环开始时,首先会从宏任务队列中取出一个宏任务执行。在执行这个宏任务的过程中,可能会产生新的宏任务或微任务,新产生的宏任务会被放入宏任务队列的末尾,新产生的微任务会被放入微任务队列的末尾。
- 微任务队列(Micro - Task Queue):
- 微任务是由JavaScript语言层面提供的异步任务,常见的微任务有
Promise.then
(.catch
和.finally
本质上也是.then
的变体)、MutationObserver
、process.nextTick
(仅在Node.js环境中有)。
- 当一个宏任务执行完毕后,事件循环会检查微任务队列,如果微任务队列中有任务,就会依次执行微任务队列中的所有任务,直到微任务队列为空,然后再从宏任务队列中取出下一个宏任务执行。
Promise和Async/Await在这两个队列中的执行顺序和交互机制
- Promise:
Promise
的构造函数是同步执行的,而Promise.then
回调函数是异步的,会被放入微任务队列。例如:
console.log('start');
const p = new Promise((resolve) => {
console.log('Promise constructor');
resolve();
});
p.then(() => {
console.log('Promise.then');
});
console.log('end');
- 输出结果为:
start
,Promise constructor
,end
,Promise.then
。这是因为Promise
构造函数同步执行,then
回调放入微任务队列,等同步代码执行完后,事件循环会处理微任务队列中的Promise.then
回调。
- Async/Await:
async
函数返回一个Promise
。await
关键字只能在async
函数内部使用,它会暂停async
函数的执行,等待Promise
被解决(resolved)或被拒绝(rejected)。当await
后的Promise
被解决时,await
表达式的值就是该Promise
的解决值。并且await
后的代码会被包装成一个微任务。例如:
async function asyncFunc() {
console.log('asyncFunc start');
await Promise.resolve();
console.log('asyncFunc after await');
}
asyncFunc();
console.log('global');
- 输出结果为:
asyncFunc start
,global
,asyncFunc after await
。这是因为await Promise.resolve()
会暂停asyncFunc
执行,将await
后的代码放入微任务队列,然后继续执行同步代码console.log('global')
,最后事件循环处理微任务队列,执行asyncFunc
中await
后的代码。
复杂代码示例及分析
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => {
console.log('3');
});
setTimeout(() => {
console.log('4');
}, 0);
}, 0);
async function asyncFunc() {
console.log('5');
await Promise.resolve();
console.log('6');
}
asyncFunc();
Promise.resolve().then(() => {
console.log('7');
});
console.log('8');
- 执行过程和输出结果:
- 首先,同步代码执行,输出
1
,5
,8
。
- 然后,
asyncFunc
函数中await Promise.resolve()
将console.log('6')
包装成微任务放入微任务队列,Promise.resolve().then(() => { console.log('7'); })
也将回调放入微任务队列。
- 接着,宏任务队列中有
setTimeout(() => { console.log('2'); Promise.resolve().then(() => { console.log('3'); }); setTimeout(() => { console.log('4'); }, 0); }, 0);
这个宏任务,开始执行。输出2
,Promise.resolve().then(() => { console.log('3'); })
将回调放入微任务队列,setTimeout(() => { console.log('4'); }, 0);
将这个新的宏任务放入宏任务队列。
- 此时宏任务执行完毕,开始处理微任务队列,依次输出
7
,6
,3
。
- 微任务队列处理完毕,从宏任务队列中取出新的
setTimeout(() => { console.log('4'); }, 0);
宏任务执行,输出4
。
- 最终输出结果为:
1
,5
,8
,2
,7
,6
,3
,4
。
- 原因解释:
- 同步代码按顺序执行。
async/await
和Promise.then
产生的微任务在宏任务执行完毕后按顺序执行。
- 宏任务队列中的任务按顺序执行,每个宏任务执行过程中产生的新宏任务放入宏任务队列末尾,新微任务放入微任务队列末尾。