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
- Execute synchronous code (execution stack)
- Execute all microtasks
- Execute one macrotask
- 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
javascriptconsole.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
javascriptconsole.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
javascriptconst 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
javascriptPromise.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
javascriptPromise.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
javascriptfunction 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
javascriptfunction 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
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) { // Use microtask to yield control, avoid blocking rendering Promise.resolve().then(processChunk); } } processChunk(); }
Common Interview Questions
1. Execution Order of Promise and setTimeout
javascriptsetTimeout(() => { 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
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')); // 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
javascriptconsole.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:
javascriptprocess.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
- Microtasks have higher priority than macrotasks: Microtasks execute immediately after the current macrotask completes
- Promise.then is a microtask: Promise callbacks execute in the microtask queue
- Event loop execution order: Synchronous code → Microtasks → Macrotask → Microtasks → Macrotask...
- Chaining execution order: Each then creates a microtask, executed in order
- Promise constructor is synchronous: Code in constructor executes immediately
- Use microtasks appropriately: Avoid unnecessary microtasks, be aware of performance impact
- Node.js differences: process.nextTick has higher priority than Promise.then