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

What is the Promise microtask mechanism? How to understand the event loop?

2月22日 14:06

The Promise microtask mechanism is an important concept in the JavaScript event loop. Understanding it is crucial for mastering asynchronous programming and solving complex asynchronous problems.

Event Loop Basics

JavaScript is single-threaded and handles asynchronous operations through the event loop. The event loop coordinates the execution stack, macrotask queue, and microtask queue.

Event Loop Execution Order

  1. Execute synchronous code (execution stack)
  2. Execute all microtasks
  3. Execute one macrotask
  4. Repeat steps 2-3

Microtask vs Macrotask

Microtask

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

Macrotask

  • setTimeout
  • setInterval
  • setImmediate (Node.js)
  • I/O operations
  • UI rendering

Execution Order Examples

Basic Example

javascript
console.log('1. Synchronous code'); setTimeout(() => { console.log('2. setTimeout (macrotask)'); }, 0); Promise.resolve().then(() => { console.log('3. Promise.then (microtask)'); }); console.log('4. Synchronous code'); // Output order: // 1. Synchronous code // 4. Synchronous code // 3. Promise.then (microtask) // 2. setTimeout (macrotask)

Complex Example

javascript
console.log('1. Start'); setTimeout(() => { console.log('2. setTimeout 1'); Promise.resolve().then(() => { console.log('3. Microtask in setTimeout 1'); }); }, 0); Promise.resolve().then(() => { console.log('4. Promise.then 1'); setTimeout(() => { console.log('5. setTimeout in Promise.then 1'); }, 0); }); Promise.resolve().then(() => { console.log('6. Promise.then 2'); }); setTimeout(() => { console.log('7. setTimeout 2'); }, 0); console.log('8. End'); // Output order: // 1. Start // 8. End // 4. Promise.then 1 // 6. Promise.then 2 // 2. setTimeout 1 // 7. setTimeout 2 // 3. Microtask in setTimeout 1 // 5. setTimeout in Promise.then 1

Promise Microtask Mechanism

Promise State Change Triggers Microtask

javascript
const promise = new Promise((resolve) => { console.log('1. Promise constructor (synchronous)'); resolve('Success'); }); promise.then(() => { console.log('2. Promise.then (microtask)'); }); console.log('3. Synchronous code'); // Output order: // 1. Promise constructor (synchronous) // 3. Synchronous code // 2. Promise.then (microtask)

Microtasks in Chaining

javascript
Promise.resolve() .then(() => { console.log('1. First then'); return 'Result'; }) .then(() => { console.log('2. Second then'); }) .then(() => { console.log('3. Third then'); }); console.log('4. Synchronous code'); // Output order: // 4. Synchronous code // 1. First then // 2. Second then // 3. Third then

Microtasks in Nested Promises

javascript
Promise.resolve() .then(() => { console.log('1. Outer then'); Promise.resolve().then(() => { console.log('2. Inner then'); }); }) .then(() => { console.log('3. Outer second then'); }); console.log('4. Synchronous code'); // Output order: // 4. Synchronous code // 1. Outer then // 2. Inner then // 3. Outer second then

Practical Use Cases

1. Ensure Code Executes in Next Event Loop

javascript
function nextTick(callback) { Promise.resolve().then(callback); } console.log('1. Start'); nextTick(() => { console.log('2. Next event loop'); }); console.log('3. End'); // Output order: // 1. Start // 3. End // 2. Next event loop

2. Batch DOM Updates

javascript
function batchUpdate(updates) { // Use microtask to ensure execution after current event loop Promise.resolve().then(() => { updates.forEach(update => { update(); }); }); } // Usage example batchUpdate([ () => document.body.style.backgroundColor = 'red', () => document.body.style.color = 'white', () => document.body.style.fontSize = '16px' ]);

3. Avoid Blocking Rendering

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) { // Use microtask to yield control, avoid blocking rendering Promise.resolve().then(processChunk); } } processChunk(); }

Common Interview Questions

1. Execution Order of Promise and setTimeout

javascript
setTimeout(() => { console.log('1. setTimeout'); }, 0); Promise.resolve().then(() => { console.log('2. Promise.then'); }); // Output order: // 2. Promise.then // 1. setTimeout

Reason: Microtasks have higher priority than macrotasks and execute immediately after the current macrotask completes.

2. Execution Order of Multiple Promises

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')); // Output order: // 1 // 4 // 2 // 5 // 3 // 6

Reason: The first then of each Promise chain is a microtask, executed in registration order.

3. Synchronous Code in Promise Constructor

javascript
console.log('1. Start'); const promise = new Promise((resolve) => { console.log('2. Promise constructor'); resolve(); }); promise.then(() => { console.log('3. Promise.then'); }); console.log('4. End'); // Output order: // 1. Start // 2. Promise constructor // 4. End // 3. Promise.then

Reason: Code in Promise constructor is synchronous, then callback is a microtask.

Performance Considerations

1. Avoid Excessively Long Microtask Chains

javascript
// Not recommended: long microtask chains may cause performance issues function processItems(items) { return items.reduce((promise, item) => { return promise.then(() => processItem(item)); }, Promise.resolve()); } // Recommended: use Promise.all for parallel processing function processItems(items) { return Promise.all(items.map(item => processItem(item))); }

2. Use Microtasks Appropriately

javascript
// Not recommended: unnecessary microtask function doSomething() { Promise.resolve().then(() => { console.log('Execute'); }); } // Recommended: execute synchronous code directly function doSomething() { console.log('Execute'); }

Microtasks in Node.js

The microtask mechanism in Node.js is slightly different from browsers:

javascript
process.nextTick(() => { console.log('1. process.nextTick'); }); Promise.resolve().then(() => { console.log('2. Promise.then'); }); setImmediate(() => { console.log('3. setImmediate'); }); // Output order: // 1. process.nextTick // 2. Promise.then // 3. setImmediate

Note: process.nextTick has higher priority than Promise.then.

Summary

  1. Microtasks have higher priority than macrotasks: Microtasks execute immediately after the current macrotask completes
  2. Promise.then is a microtask: Promise callbacks execute in the microtask queue
  3. Event loop execution order: Synchronous code → Microtasks → Macrotask → Microtasks → Macrotask...
  4. Chaining execution order: Each then creates a microtask, executed in order
  5. Promise constructor is synchronous: Code in constructor executes immediately
  6. Use microtasks appropriately: Avoid unnecessary microtasks, be aware of performance impact
  7. Node.js differences: process.nextTick has higher priority than Promise.then
标签:Promise