乐闻世界logo
搜索文章和话题

Promise 的微任务机制是什么?如何理解事件循环?

2月22日 14:06

Promise 的微任务机制是 JavaScript 事件循环中的重要概念,理解它对于掌握异步编程和解决复杂的异步问题至关重要。

事件循环基础

JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环负责协调执行栈、宏任务队列和微任务队列。

事件循环的执行顺序

  1. 执行同步代码(执行栈)
  2. 执行所有微任务
  3. 执行一个宏任务
  4. 重复步骤 2-3

微任务 vs 宏任务

微任务(Microtask)

  • Promise.then/catch/finally
  • queueMicrotask()
  • MutationObserver

宏任务(Macrotask)

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 操作
  • UI 渲染

执行顺序示例

基本示例

javascript
console.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(宏任务)

复杂示例

javascript
console.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 状态改变触发微任务

javascript
const 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(微任务)

链式调用的微任务

javascript
Promise.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 的微任务

javascript
Promise.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. 确保代码在下一个事件循环执行

javascript
function nextTick(callback) { Promise.resolve().then(callback); } console.log('1. 开始'); nextTick(() => { console.log('2. 下一个事件循环'); }); console.log('3. 结束'); // 输出顺序: // 1. 开始 // 3. 结束 // 2. 下一个事件循环

2. 批量更新 DOM

javascript
function 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. 避免阻塞渲染

javascript
function 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 的执行顺序

javascript
setTimeout(() => { console.log('1. setTimeout'); }, 0); Promise.resolve().then(() => { console.log('2. Promise.then'); }); // 输出顺序: // 2. Promise.then // 1. setTimeout

原因:微任务优先级高于宏任务,会在当前宏任务执行完后立即执行。

2. 多个 Promise 的执行顺序

javascript
Promise.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 构造函数中的同步代码

javascript
console.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 中的微任务机制与浏览器略有不同:

javascript
process.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。

总结

  1. 微任务优先级高于宏任务:微任务会在当前宏任务执行完后立即执行
  2. Promise.then 是微任务:Promise 的回调会在微任务队列中执行
  3. 事件循环的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → 宏任务...
  4. 链式调用的执行顺序:每个 then 都会创建一个微任务,按顺序执行
  5. Promise 构造函数是同步的:构造函数中的代码立即执行
  6. 合理使用微任务:避免不必要的微任务,注意性能影响
  7. Node.js 的差异:process.nextTick 优先级高于 Promise.then
标签:Promise