Macro tasks and micro tasks
Concepts of Macro-tasks and Micro-tasks
Macro-tasks and micro-tasks are two types of task queues in JavaScript that determine the order of code execution. Macro-tasks include the entire script code, setTimeout
, setInterval
, I/O operations, UI rendering, etc. Micro-tasks include Promise.then
, process.nextTick
, MutationObserver
, etc. Understanding their differences is crucial for mastering asynchronous programming in JavaScript.
Execution Order in the Event Loop
The JavaScript engine follows a specific order when executing code:
- Execute a macro-task (usually the entire script code)
- During execution, if micro-tasks are encountered, add them to the micro-task queue
- After the macro-task completes, immediately execute all micro-tasks
- Perform UI rendering (in browser environments)
- Start the next macro-task
console.log('script start'); // Macro-task
setTimeout(() => {
console.log('setTimeout'); // Macro-task
}, 0);
Promise.resolve().then(() => {
console.log('promise1'); // Micro-task
}).then(() => {
console.log('promise2'); // Micro-task
});
console.log('script end'); // Macro-task
// Output order:
// script start
// script end
// promise1
// promise2
// setTimeout
process.nextTick
in Node.js
In Node.js, micro-tasks created by process.nextTick
have higher priority than those created by Promise.then
:
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// Output order:
// nextTick
// promise
Nesting of Macro-tasks and Micro-tasks
When new micro-tasks are generated within a micro-task, they continue to execute until the micro-task queue is empty:
function microtaskLoop() {
Promise.resolve().then(() => {
console.log('microtask');
microtaskLoop(); // Infinite loop
});
}
microtaskLoop();
setTimeout(() => console.log('timeout'), 0);
// Will continuously output 'microtask' and never execute 'timeout'
Differences Between Browsers and Node.js
The implementation of macro-tasks and micro-tasks varies across environments:
-
Browser Environment:
- Macro-tasks:
setTimeout
,setInterval
,requestAnimationFrame
, I/O, UI rendering - Micro-tasks:
Promise.then
,MutationObserver
- Macro-tasks:
-
Node.js Environment:
- Macro-tasks:
setTimeout
,setInterval
,setImmediate
, I/O - Micro-tasks:
process.nextTick
,Promise.then
- Macro-tasks:
// Execution order example in Node.js
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));
// Possible output order:
// nextTick
// promise
// timeout
// immediate
Practical Application Scenarios
- Batch DOM Updates:
// Use micro-tasks to batch process DOM updates
function batchUpdate() {
let pending = false;
const callbacks = [];
return function(callback) {
callbacks.push(callback);
if (!pending) {
pending = true;
Promise.resolve().then(() => {
const copies = callbacks.slice();
callbacks.length = 0;
pending = false;
copies.forEach(cb => cb());
});
}
};
}
const update = batchUpdate();
update(() => console.log('Update 1'));
update(() => console.log('Update 2'));
- Priority Control:
// Ensure critical tasks are executed first
function criticalTask() {
process.nextTick(() => {
console.log('Critical task');
});
}
function normalTask() {
Promise.resolve().then(() => {
console.log('Normal task');
});
}
criticalTask();
normalTask();
// Output:
// Critical task
// Normal task
Performance Considerations
- Micro-task Accumulation: Too many micro-tasks can block the main thread
- Task Splitting: Long-running tasks should be split into multiple macro-tasks
- Priority Selection: Choose the appropriate task type based on the scenario
// Bad practice - Infinite micro-task loop
function badPractice() {
Promise.resolve().then(badPractice);
}
// Good practice - Split using macro-tasks
function goodPractice() {
// Process part of the work
if (moreWork) {
setTimeout(goodPractice, 0);
}
}
Common Misconceptions
- Assuming
setTimeout(fn, 0)
executes immediately: It actually just adds the task to the macro-task queue as soon as possible - Ignoring micro-task priority: Micro-tasks are executed entirely before the next macro-task
- Confusing implementations between Node.js and browsers: Especially the behavior of
process.nextTick
andsetImmediate
// Misconception example
console.log('start');
setTimeout(() => {
console.log('timeout 1');
Promise.resolve().then(() => console.log('promise 1'));
}, 0);
setTimeout(() => {
console.log('timeout 2');
Promise.resolve().then(() => console.log('promise 2'));
}, 0);
console.log('end');
// Actual output:
// start
// end
// timeout 1
// promise 1
// timeout 2
// promise 2
Advanced Application Patterns
- Task Scheduler:
class TaskScheduler {
constructor() {
this.macroTasks = [];
this.microTasks = [];
this.isProcessing = false;
}
addMacroTask(task) {
this.macroTasks.push(task);
this.schedule();
}
addMicroTask(task) {
this.microTasks.push(task);
this.schedule();
}
schedule() {
if (this.isProcessing) return;
this.isProcessing = true;
Promise.resolve().then(() => {
// Execute all micro-tasks first
while (this.microTasks.length) {
const task = this.microTasks.shift();
task();
}
// Then execute one macro-task
if (this.macroTasks.length) {
const task = this.macroTasks.shift();
task();
}
this.isProcessing = false;
if (this.microTasks.length || this.macroTasks.length) {
this.schedule();
}
});
}
}
- Asynchronous Queue Control:
async function processTasks(tasks, concurrency = 4) {
const results = [];
let index = 0;
async function runNext() {
if (index >= tasks.length) return;
const currentIndex = index++;
const task = tasks[currentIndex];
results[currentIndex] = await task();
await Promise.resolve(); // Micro-task checkpoint
await runNext();
}
const workers = Array(concurrency).fill().map(runNext);
await Promise.all(workers);
return results;
}
Debugging Tips
- Use
console.log
to track execution order - Leverage the Performance panel in Chrome DevTools
- Use the
--trace-event-categories
parameter in Node.js
// Debugging example
function debugFlow() {
console.log('Synchronous code starts');
setTimeout(() => {
console.log('Macro-task 1');
Promise.resolve().then(() => console.log('Micro-task from macro-task 1'));
}, 0);
Promise.resolve().then(() => {
console.log('Micro-task 1');
setTimeout(() => console.log('Macro-task from micro-task 1'), 0);
});
console.log('Synchronous code ends');
}
debugFlow();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:代理服务器配置