The microtask queue feature of Promise
The Relationship Between Promises and the Microtask Queue
The Promise object introduced in ECMAScript 6 is closely related to JavaScript's event loop mechanism, particularly in that its callbacks are placed in the microtask queue rather than the macrotask queue. This design ensures that Promise callbacks are executed earlier than macrotasks like setTimeout
, even if they are created within the same event loop.
console.log('Script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
}).then(() => {
console.log('Promise 2');
});
console.log('Script end');
// Output order:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout
Execution Mechanism of the Microtask Queue
The microtask queue is executed immediately after the current macrotask completes and before rendering. This means microtasks can block rendering because they are all executed before the browser has a chance to paint a new frame. Each microtask execution may generate new microtasks, and the engine will continue processing the microtask queue until it is empty.
function recursiveMicrotask(count = 0) {
if (count >= 3) return;
Promise.resolve().then(() => {
console.log(`Microtask ${count}`);
recursiveMicrotask(count + 1);
});
}
recursiveMicrotask();
setTimeout(() => console.log('Macrotask'), 0);
// Output order:
// Microtask 0
// Microtask 1
// Microtask 2
// Macrotask
Promise Chains and Microtask Generation
Each then
/catch
/finally
method of a Promise returns a new Promise, and the callbacks of these methods are scheduled as microtasks. When a Promise's state changes, all related callbacks are batched into the microtask queue.
const promise = new Promise((resolve) => {
console.log('Executor');
resolve('Initial');
});
promise
.then((value) => {
console.log('Then 1:', value);
return 'Modified';
})
.then((value) => {
console.log('Then 2:', value);
throw new Error('Failure');
})
.catch((error) => {
console.log('Catch:', error.message);
return 'Recovered';
})
.finally(() => {
console.log('Finally');
});
console.log('Sync code');
// Output order:
// Executor
// Sync code
// Then 1: Initial
// Then 2: Modified
// Catch: Failure
// Finally
Interaction with async/await
Async functions are essentially based on Promises. The await
expression pauses function execution and wraps the remaining code as a microtask. This makes the execution order of async/await code similar to Promise chains but with more intuitive syntax.
async function asyncFunc() {
console.log('Async start');
await Promise.resolve();
console.log('After await');
}
console.log('Script start');
asyncFunc();
console.log('Script end');
// Output order:
// Script start
// Async start
// Script end
// After await
Microtask Applications in Browser APIs
Many modern browser APIs, such as MutationObserver
and queueMicrotask
, utilize the microtask queue. Understanding the microtask nature of Promises helps in correctly handling interactions with these APIs.
// Using queueMicrotask to directly schedule a microtask
queueMicrotask(() => {
console.log('Microtask via queueMicrotask');
});
Promise.resolve().then(() => {
console.log('Microtask via Promise');
});
// Output order:
// Microtask via Promise
// Microtask via queueMicrotask
Performance Considerations and Potential Issues
Excessive use of microtasks can lead to prolonged blocking of the main thread, affecting page responsiveness. Especially when processing large amounts of data, consider chunking tasks or using requestIdleCallback
.
function processLargeData() {
const data = Array(10000).fill().map((_, i) => i);
// Bad practice: processing all data at once
// Promise.resolve().then(() => {
// data.forEach(heavyProcessing);
// });
// Better approach: chunked processing
function processChunk(start) {
if (start >= data.length) return;
const end = Math.min(start + 100, data.length);
queueMicrotask(() => {
for (let i = start; i < end; i++) {
heavyProcessing(data[i]);
}
processChunk(end);
});
}
processChunk(0);
}
function heavyProcessing(item) {
// Simulate time-consuming operation
for (let i = 0; i < 100000; i++);
}
Comparison with Other Asynchronous Patterns
Compared to scheduling methods like setImmediate
, setTimeout(0)
, and requestAnimationFrame
, Promise microtasks provide higher-priority execution timing, making them suitable for operations that need to execute as soon as possible without blocking UI updates.
setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
requestAnimationFrame(() => console.log('rAF'));
queueMicrotask(() => console.log('queueMicrotask'));
// Typical output order:
// Promise
// queueMicrotask
// rAF
// setTimeout
Differences in the Node.js Environment
In Node.js, process.nextTick
has even higher priority than Promise microtasks. Additionally, Node.js has its own implementation of the microtask queue, which differs slightly from the browser environment.
process.nextTick(() => console.log('nextTick 1'));
Promise.resolve().then(() => console.log('Promise 1'));
process.nextTick(() => console.log('nextTick 2'));
Promise.resolve().then(() => console.log('Promise 2'));
// Node.js output order:
// nextTick 1
// nextTick 2
// Promise 1
// Promise 2
Analysis of Practical Use Cases
Leveraging the microtask nature of Promises enables efficient batch DOM operations, update scheduling in state management libraries, and more. Frameworks like Vue.js utilize this feature to optimize change detection.
// Simulating batch updates in a state management library
let isUpdating = false;
let pendingStates = [];
function setState(newState) {
pendingStates.push(newState);
if (!isUpdating) {
isUpdating = true;
Promise.resolve().then(() => {
const states = [...pendingStates];
pendingStates = [];
isUpdating = false;
applyStates(states);
});
}
}
function applyStates(states) {
console.log('Applying states:', states);
// In practice, this would merge states and update components
}
setState({ count: 1 });
setState({ count: 2 });
console.log('Sync code');
// Output order:
// Sync code
// Applying states: [ { count: 1 }, { count: 2 } ]
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Promise的静态方法
下一篇:class关键字基本语法