Promise 的微任务机制是 JavaScript 事件循环中的重要概念,理解它对于掌握异步编程和解决复杂的异步问题至关重要。
事件循环基础
JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环负责协调执行栈、宏任务队列和微任务队列。
事件循环的执行顺序
- 执行同步代码(执行栈)
- 执行所有微任务
- 执行一个宏任务
- 重复步骤 2-3
微任务 vs 宏任务
微任务(Microtask)
- Promise.then/catch/finally
- queueMicrotask()
- MutationObserver
宏任务(Macrotask)
- setTimeout
- setInterval
- setImmediate(Node.js)
- I/O 操作
- UI 渲染
执行顺序示例
基本示例
javascriptconsole.log('1. 同步代码'); setTimeout(() => { console.log('2. setTimeout(宏任务)'); }, 0); Promise.resolve().then(() => { console.log('3. Promise.then(微任务)'); }); console.log('4. 同步代码'); // 输出顺序: // 1. 同步代码 // 4. 同步代码 // 3. Promise.then(微任务) // 2. setTimeout(宏任务)
复杂示例
javascriptconsole.log('1. 开始'); setTimeout(() => { console.log('2. setTimeout 1'); Promise.resolve().then(() => { console.log('3. setTimeout 1 中的微任务'); }); }, 0); Promise.resolve().then(() => { console.log('4. Promise.then 1'); setTimeout(() => { console.log('5. Promise.then 1 中的 setTimeout'); }, 0); }); Promise.resolve().then(() => { console.log('6. Promise.then 2'); }); setTimeout(() => { console.log('7. setTimeout 2'); }, 0); console.log('8. 结束'); // 输出顺序: // 1. 开始 // 8. 结束 // 4. Promise.then 1 // 6. Promise.then 2 // 2. setTimeout 1 // 7. setTimeout 2 // 3. setTimeout 1 中的微任务 // 5. Promise.then 1 中的 setTimeout
Promise 的微任务机制
Promise 状态改变触发微任务
javascriptconst promise = new Promise((resolve) => { console.log('1. Promise 构造函数(同步)'); resolve('成功'); }); promise.then(() => { console.log('2. Promise.then(微任务)'); }); console.log('3. 同步代码'); // 输出顺序: // 1. Promise 构造函数(同步) // 3. 同步代码 // 2. Promise.then(微任务)
链式调用的微任务
javascriptPromise.resolve() .then(() => { console.log('1. 第一个 then'); return '结果'; }) .then(() => { console.log('2. 第二个 then'); }) .then(() => { console.log('3. 第三个 then'); }); console.log('4. 同步代码'); // 输出顺序: // 4. 同步代码 // 1. 第一个 then // 2. 第二个 then // 3. 第三个 then
嵌套 Promise 的微任务
javascriptPromise.resolve() .then(() => { console.log('1. 外层 then'); Promise.resolve().then(() => { console.log('2. 内层 then'); }); }) .then(() => { console.log('3. 外层第二个 then'); }); console.log('4. 同步代码'); // 输出顺序: // 4. 同步代码 // 1. 外层 then // 2. 内层 then // 3. 外层第二个 then
实际应用场景
1. 确保代码在下一个事件循环执行
javascriptfunction nextTick(callback) { Promise.resolve().then(callback); } console.log('1. 开始'); nextTick(() => { console.log('2. 下一个事件循环'); }); console.log('3. 结束'); // 输出顺序: // 1. 开始 // 3. 结束 // 2. 下一个事件循环
2. 批量更新 DOM
javascriptfunction batchUpdate(updates) { // 使用微任务确保在当前事件循环结束后执行 Promise.resolve().then(() => { updates.forEach(update => { update(); }); }); } // 使用示例 batchUpdate([ () => document.body.style.backgroundColor = 'red', () => document.body.style.color = 'white', () => document.body.style.fontSize = '16px' ]);
3. 避免阻塞渲染
javascriptfunction processLargeData(data) { let index = 0; function processChunk() { const chunkSize = 1000; const end = Math.min(index + chunkSize, data.length); for (; index < end; index++) { processDataItem(data[index]); } if (index < data.length) { // 使用微任务让出控制权,避免阻塞渲染 Promise.resolve().then(processChunk); } } processChunk(); }
常见面试题
1. Promise 和 setTimeout 的执行顺序
javascriptsetTimeout(() => { console.log('1. setTimeout'); }, 0); Promise.resolve().then(() => { console.log('2. Promise.then'); }); // 输出顺序: // 2. Promise.then // 1. setTimeout
原因:微任务优先级高于宏任务,会在当前宏任务执行完后立即执行。
2. 多个 Promise 的执行顺序
javascriptPromise.resolve() .then(() => console.log('1')) .then(() => console.log('2')) .then(() => console.log('3')); Promise.resolve() .then(() => console.log('4')) .then(() => console.log('5')) .then(() => console.log('6')); // 输出顺序: // 1 // 4 // 2 // 5 // 3 // 6
原因:每个 Promise 链的第一个 then 都是微任务,按照注册顺序执行。
3. Promise 构造函数中的同步代码
javascriptconsole.log('1. 开始'); const promise = new Promise((resolve) => { console.log('2. Promise 构造函数'); resolve(); }); promise.then(() => { console.log('3. Promise.then'); }); console.log('4. 结束'); // 输出顺序: // 1. 开始 // 2. Promise 构造函数 // 4. 结束 // 3. Promise.then
原因:Promise 构造函数中的代码是同步执行的,then 回调是微任务。
性能考虑
1. 避免过长的微任务链
javascript// 不推荐:过长的微任务链可能导致性能问题 function processItems(items) { return items.reduce((promise, item) => { return promise.then(() => processItem(item)); }, Promise.resolve()); } // 推荐:使用 Promise.all 并行处理 function processItems(items) { return Promise.all(items.map(item => processItem(item))); }
2. 合理使用微任务
javascript// 不推荐:不必要的微任务 function doSomething() { Promise.resolve().then(() => { console.log('执行'); }); } // 推荐:直接执行同步代码 function doSomething() { console.log('执行'); }
Node.js 中的微任务
Node.js 中的微任务机制与浏览器略有不同:
javascriptprocess.nextTick(() => { console.log('1. process.nextTick'); }); Promise.resolve().then(() => { console.log('2. Promise.then'); }); setImmediate(() => { console.log('3. setImmediate'); }); // 输出顺序: // 1. process.nextTick // 2. Promise.then // 3. setImmediate
注意:process.nextTick 的优先级高于 Promise.then。
总结
- 微任务优先级高于宏任务:微任务会在当前宏任务执行完后立即执行
- Promise.then 是微任务:Promise 的回调会在微任务队列中执行
- 事件循环的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → 宏任务...
- 链式调用的执行顺序:每个 then 都会创建一个微任务,按顺序执行
- Promise 构造函数是同步的:构造函数中的代码立即执行
- 合理使用微任务:避免不必要的微任务,注意性能影响
- Node.js 的差异:process.nextTick 优先级高于 Promise.then