您现在的位置是:网站首页 > 宏任务与微任务文章详情

宏任务与微任务

宏任务与微任务的概念

宏任务和微任务是JavaScript中任务队列的两种类型,它们决定了代码的执行顺序。宏任务包括setTimeout、setInterval、I/O操作等,而微任务则包括Promise.then、process.nextTick等。理解它们的区别对于编写高效的异步代码至关重要。

console.log('script start');

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

Promise.resolve().then(() => {
  console.log('promise1');
}).then(() => {
  console.log('promise2');
});

console.log('script end');

事件循环中的执行顺序

JavaScript引擎在处理异步代码时遵循特定顺序:

  1. 执行当前宏任务
  2. 执行所有微任务
  3. 渲染UI(浏览器环境)
  4. 执行下一个宏任务
setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => {
  console.log('timeout2');
  Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);

Promise.resolve().then(() => console.log('promise1'));
Promise.resolve().then(() => console.log('promise2'));

Node.js中的特殊处理

Node.js的事件循环与浏览器略有不同,它分为多个阶段:

  • timers:执行setTimeout和setInterval回调
  • pending callbacks:执行系统操作回调
  • idle, prepare:内部使用
  • poll:检索新的I/O事件
  • check:执行setImmediate回调
  • close callbacks:执行关闭事件回调
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);

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

微任务的优先级差异

在Node.js中,process.nextTick的优先级高于Promise:

  1. process.nextTick队列
  2. Promise微任务队列
  3. 其他微任务
process.nextTick(() => console.log('nextTick1'));
process.nextTick(() => {
  console.log('nextTick2');
  process.nextTick(() => console.log('nextTick inside nextTick'));
});

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

process.nextTick(() => console.log('nextTick3'));

实际应用中的陷阱

不正确的任务使用可能导致性能问题或意外行为:

// 递归的process.nextTick会导致I/O饥饿
function recursiveNextTick() {
  process.nextTick(() => {
    console.log('running nextTick');
    recursiveNextTick();
  });
}

// 更合理的做法是使用setImmediate
function betterRecursive() {
  setImmediate(() => {
    console.log('running setImmediate');
    betterRecursive();
  });
}

宏任务与微任务的性能影响

微任务会在当前宏任务结束后立即执行,而宏任务需要等待事件循环的下一个周期:

function measurePerformance() {
  console.time('microtasks');
  let count = 0;
  
  function runMicrotasks() {
    if (count++ < 10000) {
      Promise.resolve().then(runMicrotasks);
    } else {
      console.timeEnd('microtasks');
    }
  }
  
  runMicrotasks();
  
  console.time('macrotasks');
  count = 0;
  
  function runMacrotasks() {
    if (count++ < 10000) {
      setTimeout(runMacrotasks, 0);
    } else {
      console.timeEnd('macrotasks');
    }
  }
  
  runMacrotasks();
}

混合使用的复杂场景

当宏任务和微任务混合使用时,执行顺序可能变得复杂:

setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => {
  console.log('timeout2');
  Promise.resolve().then(() => console.log('promise inside timeout'));
}, 0);

Promise.resolve().then(() => {
  console.log('promise1');
  setTimeout(() => console.log('timeout inside promise'), 0);
});

process.nextTick(() => {
  console.log('nextTick1');
  process.nextTick(() => console.log('nextTick inside nextTick'));
});

不同环境下的行为差异

浏览器和Node.js在处理微任务时存在差异:

// 浏览器中的表现
button.addEventListener('click', () => {
  Promise.resolve().then(() => console.log('microtask'));
  console.log('listener');
});

// Node.js中的http模块
const server = require('http').createServer();

server.on('request', (req, res) => {
  Promise.resolve().then(() => console.log('microtask'));
  res.end('hello');
  console.log('request');
});

调试技巧

使用调试工具可以更好地理解任务执行顺序:

// 添加标签以便在调试工具中识别
queueMicrotask(() => {
  console.log('labeled microtask');
});

// 使用async/await
async function debugAsync() {
  console.log('before await');
  await Promise.resolve();
  console.log('after await');
}

最佳实践建议

  1. 避免在微任务中执行耗时操作
  2. 谨慎使用递归的process.nextTick
  3. 对于大量异步操作,考虑使用setImmediate而非setTimeout
  4. 理解不同环境下的事件循环差异
// 良好的任务分割
function processChunk(data, callback) {
  let index = 0;
  
  function next() {
    if (index < data.length) {
      // 处理一部分数据
      const chunk = data.slice(index, index + 100);
      index += 100;
      
      // 使用setImmediate避免阻塞
      setImmediate(() => {
        processChunkSync(chunk);
        next();
      });
    } else {
      callback();
    }
  }
  
  next();
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步