Skip to content

Node.js 宏任务与微任务详解

概述

是的,Node.js中确实有宏任务和微任务的概念!这是Node.js事件循环机制的核心组成部分。

什么是宏任务和微任务?

宏任务 (Macro Tasks)

宏任务是由事件循环的各个阶段处理的任务,包括:

javascript
// 宏任务示例
setTimeout(() => console.log('setTimeout'), 0);     // Timer阶段
setImmediate(() => console.log('setImmediate'));    // Check阶段
fs.readFile('file.txt', callback);                 // Poll阶段
setInterval(() => console.log('setInterval'), 100); // Timer阶段

微任务 (Micro Tasks)

微任务是在每个事件循环阶段之间执行的任务,包括:

javascript
// 微任务示例
process.nextTick(() => console.log('nextTick'));           // 最高优先级
Promise.resolve().then(() => console.log('Promise'));      // Promise回调
queueMicrotask(() => console.log('queueMicrotask'));       // 通用微任务API

Node.js 事件循环详解

事件循环的6个阶段

   ┌───────────────────────────┐
┌─>│           timers          │  ← setTimeout, setInterval
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  ← I/O异常回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  ← 内部使用
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           poll            │  ← 获取新的I/O事件,执行I/O回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │           check           │  ← setImmediate回调
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  ← socket.on('close', ...)
   └───────────────────────────┘

微任务的执行时机

关键点:微任务在每个阶段之间执行!

每个阶段 → 执行微任务队列 → 下个阶段 → 执行微任务队列 → ...

任务优先级

执行顺序(从高到低)

  1. 同步代码 - 立即执行
  2. process.nextTick - 微任务,最高优先级
  3. Promise.then/catch/finally - 微任务
  4. queueMicrotask - 微任务
  5. setImmediate - 宏任务(Check阶段)
  6. setTimeout/setInterval - 宏任务(Timer阶段)
  7. I/O操作回调 - 宏任务(Poll阶段)

微任务内部优先级

javascript
// 优先级:nextTick > Promise > queueMicrotask
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));        // 先执行
queueMicrotask(() => console.log('queueMicrotask'));

// 输出顺序:nextTick → Promise → queueMicrotask

实际执行示例

基础示例

javascript
console.log('开始');

setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));

Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));

console.log('结束');

// 输出顺序:
// 开始
// 结束
// nextTick
// Promise
// setTimeout
// setImmediate

复杂嵌套示例

javascript
setTimeout(() => {
    console.log('setTimeout');
    Promise.resolve().then(() => console.log('Promise in setTimeout'));
    process.nextTick(() => console.log('nextTick in setTimeout'));
}, 0);

Promise.resolve().then(() => {
    console.log('Promise');
    process.nextTick(() => console.log('nextTick in Promise'));
});

process.nextTick(() => {
    console.log('nextTick');
    Promise.resolve().then(() => console.log('Promise in nextTick'));
});

// 输出顺序:
// nextTick
// Promise
// Promise in nextTick
// nextTick in Promise
// setTimeout
// nextTick in setTimeout
// Promise in setTimeout

特殊情况

1. I/O回调中的执行顺序

javascript
const fs = require('fs');

fs.readFile(__filename, () => {
    setTimeout(() => console.log('setTimeout in I/O'), 0);
    setImmediate(() => console.log('setImmediate in I/O'));
});

// 在I/O回调中,setImmediate优先于setTimeout
// 输出:setImmediate in I/O → setTimeout in I/O

2. 微任务可能阻塞事件循环

javascript
// ⚠️ 危险:无限递归的微任务
function recursiveNextTick() {
    process.nextTick(recursiveNextTick);
}
recursiveNextTick(); // 会阻塞事件循环!

// ✅ 安全:使用setImmediate避免阻塞
function safeRecursive() {
    setImmediate(safeRecursive);
}
safeRecursive(); // 不会阻塞事件循环

与浏览器的区别

特性Node.js浏览器
微任务APIprocess.nextTick, PromisePromise, MutationObserver
宏任务APIsetTimeout, setImmediate, I/OsetTimeout, DOM事件
执行模型事件循环6阶段简化的事件循环
nextTick存在,优先级最高不存在
setImmediate存在不存在(部分浏览器支持)

性能考虑

1. 微任务性能对比

javascript
// 性能排序(从快到慢)
process.nextTick()     // 最快
Promise.resolve().then() // 中等
queueMicrotask()       // 较慢

2. 避免微任务饥饿

javascript
// ❌ 可能导致饥饿
for (let i = 0; i < 1000000; i++) {
    process.nextTick(() => {/* 大量微任务 */});
}

// ✅ 分批处理
function processBatch(items, batchSize = 1000) {
    const batch = items.splice(0, batchSize);
    batch.forEach(item => process.nextTick(() => processItem(item)));
    
    if (items.length > 0) {
        setImmediate(() => processBatch(items, batchSize));
    }
}

实际应用场景

1. 错误处理

javascript
// 使用process.nextTick确保错误处理在同步代码之后
function asyncFunction(callback) {
    if (typeof callback !== 'function') {
        process.nextTick(() => {
            throw new TypeError('Callback must be a function');
        });
        return;
    }
    
    // 异步操作...
}

2. API一致性

javascript
// 确保API始终异步执行
function maybeAsync(data, callback) {
    if (cache.has(data)) {
        // 即使有缓存,也要异步返回
        process.nextTick(() => callback(null, cache.get(data)));
    } else {
        fetchData(data, callback);
    }
}

3. 状态同步

javascript
// 确保状态更新在下一个tick执行
class EventEmitter {
    emit(event, ...args) {
        const listeners = this.listeners[event];
        if (listeners) {
            process.nextTick(() => {
                listeners.forEach(listener => listener(...args));
            });
        }
    }
}

调试技巧

1. 使用console.trace跟踪执行顺序

javascript
setTimeout(() => console.trace('setTimeout'), 0);
Promise.resolve().then(() => console.trace('Promise'));
process.nextTick(() => console.trace('nextTick'));

2. 监控事件循环延迟

javascript
const { performance } = require('perf_hooks');

function measureEventLoopDelay() {
    const start = performance.now();
    setImmediate(() => {
        const delay = performance.now() - start;
        console.log(`事件循环延迟: ${delay.toFixed(2)}ms`);
    });
}

setInterval(measureEventLoopDelay, 1000);

最佳实践

  1. 优先使用Promise而不是process.nextTick
  2. 避免在微任务中执行CPU密集型操作
  3. 使用setImmediate而不是setTimeout(fn, 0)
  4. 合理控制微任务数量,避免饥饿
  5. 在I/O回调中优先使用setImmediate

总结

Node.js的宏任务和微任务机制是其高性能异步处理的基础:

  • 微任务在每个事件循环阶段之间执行,优先级高
  • 宏任务按事件循环的6个阶段顺序执行
  • process.nextTick是Node.js特有的最高优先级微任务
  • 理解执行顺序有助于编写更可预测的异步代码
  • 合理使用可以提升应用性能和用户体验