美文网首页
TypeScript 中的 `queueMicrotask` 详

TypeScript 中的 `queueMicrotask` 详

作者: GTMYang | 来源:发表于2025-08-07 10:31 被阅读0次

queueMicrotask 是一个用于将微任务(microtask)加入队列的 API,它在 TypeScript/JavaScript 的异步编程中扮演重要角色。

基本概念

queueMicrotask 是 Window 和 Worker 接口提供的方法,允许你将一个函数安排为微任务执行:

queueMicrotask(() => {
  // 这里是要执行的微任务代码
});

类型定义

在 TypeScript 中,queueMicrotask 的类型定义通常如下:

declare function queueMicrotask(callback: () => void): void;

与 Promise 和 setTimeout 的区别

特性 queueMicrotask Promise.resolve().then() setTimeout
任务类型 微任务 (microtask) 微任务 (microtask) 宏任务 (macrotask)
执行时机 当前任务完成后立即执行 当前任务完成后立即执行 下一个事件循环
优先级
兼容性 现代浏览器/Node.js 广泛支持 广泛支持

使用示例

1. 基本用法

console.log('开始');

queueMicrotask(() => {
  console.log('微任务执行');
});

console.log('结束');

// 输出顺序:
// 开始
// 结束
// 微任务执行

2. 与 Promise 结合

Promise.resolve().then(() => console.log('Promise 微任务1'));
queueMicrotask(() => console.log('直接微任务'));
Promise.resolve().then(() => console.log('Promise 微任务2'));

// 输出顺序:
// 直接微任务
// Promise 微任务1
// Promise 微任务2
// (注意:不同引擎可能有微小差异)

3. 实现异步批处理

class BatchProcessor {
  private queue: (() => void)[] = [];
  private isProcessing = false;

  addTask(task: () => void): void {
    this.queue.push(task);
    
    if (!this.isProcessing) {
      this.isProcessing = true;
      queueMicrotask(() => this.processQueue());
    }
  }

  private processQueue(): void {
    while (this.queue.length > 0) {
      const task = this.queue.shift()!;
      try {
        task();
      } catch (error) {
        console.error('任务执行失败:', error);
      }
    }
    this.isProcessing = false;
  }
}

兼容性处理

对于需要支持旧环境的项目,可以添加 polyfill:

if (typeof queueMicrotask !== 'function') {
  (window as any).queueMicrotask = function(callback: () => void): void {
    Promise.resolve()
      .then(callback)
      .catch(e => setTimeout(() => { throw e; }));
  };
}

实际应用场景

  1. 性能敏感的操作:当需要尽快执行某些操作,但又不想阻塞当前任务时
  2. 状态批量更新:在 UI 框架中批量处理状态变更
  3. Promise 的替代:当不需要 Promise 的完整功能时,更轻量级的方案
  4. 测试工具:在测试中控制微任务队列

注意事项

  1. 避免无限递归

    function dangerous() {
      queueMicrotask(dangerous); // 这将导致无限微任务循环
    }
    
  2. 错误处理

    queueMicrotask(() => {
      try {
        // 可能出错的代码
      } catch (error) {
        console.error('微任务出错:', error);
      }
    });
    
  3. 性能考量:虽然微任务比宏任务快,但大量微任务仍会导致主线程阻塞

  4. 执行顺序:微任务会在当前宏任务结束前执行,但不同来源的微任务可能有不同的排队顺序

queueMicrotask 是优化异步代码流程的强大工具,合理使用可以提升应用性能和响应速度。

TypeScript 中的宏任务与微任务详解

宏任务(Macrotask)和微任务(Microtask)是 JavaScript/TypeScript 异步编程中的核心概念,理解它们的区别对于编写高效的异步代码至关重要。

基本概念对比

特性 宏任务 (Macrotask) 微任务 (Microtask)
示例 setTimeout, setInterval, I/O Promise.then, queueMicrotask
执行时机 下一个事件循环 当前事件循环末尾
优先级
执行顺序 后执行 先执行
堆栈 每次宏任务执行都有新的调用堆栈 共享当前调用堆栈

执行机制图解

[主线程执行]
   |
   |-- 同步代码执行
   |
   |-- 微任务队列检查
   |   |
   |   |-- 执行所有微任务
   |   |   |
   |   |   |-- 执行过程中可能产生新的微任务
   |   |   |
   |   |-- 直到微任务队列为空
   |
   |-- 渲染(如有需要)
   |
   |-- 从宏任务队列取一个任务执行
        |
        |-- 重复上述过程

常见宏任务和微任务

宏任务来源

  • setTimeout / setInterval
  • DOM 事件回调
  • I/O 操作 (文件读写、网络请求等)
  • requestAnimationFrame (有争议,部分浏览器实现为宏任务)
  • setImmediate (Node.js 特有)

微任务来源

  • Promise.then / catch / finally
  • queueMicrotask API
  • MutationObserver 回调
  • process.nextTick (Node.js 特有,优先级高于 Promise)

代码示例分析

console.log('脚本开始'); // 1. 同步代码

setTimeout(() => {
  console.log('setTimeout'); // 5. 宏任务
}, 0);

Promise.resolve()
  .then(() => {
    console.log('Promise 1'); // 3. 微任务
  })
  .then(() => {
    console.log('Promise 2'); // 4. 微任务
  });

queueMicrotask(() => {
  console.log('queueMicrotask'); // 2. 微任务
});

console.log('脚本结束'); // 1. 同步代码

/*
输出顺序:
1. 脚本开始
2. 脚本结束
3. queueMicrotask
4. Promise 1
5. Promise 2
6. setTimeout
*/

实际应用场景

微任务适用场景

  1. 状态批量更新:在 UI 框架中合并多次状态变更

    let needsUpdate = false;
    
    function scheduleUpdate() {
      if (!needsUpdate) {
        needsUpdate = true;
        queueMicrotask(() => {
          needsUpdate = false;
          performUpdate();
        });
      }
    }
    
  2. Promise 链式调用优化

    function fetchData() {
      return fetch('/api/data')
        .then(response => response.json())
        .then(data => processData(data)); // 微任务队列处理
    }
    

宏任务适用场景

  1. 长时间运行的任务分片

    function processLargeArray(array: any[]) {
      let index = 0;
      
      function chunk() {
        const start = Date.now();
        while (index < array.length && Date.now() - start < 50) {
          // 处理单个项目
          processItem(array[index++]);
        }
        
        if (index < array.length) {
          setTimeout(chunk, 0); // 让出主线程
        }
      }
      
      chunk();
    }
    
  2. 用户交互延迟处理

    button.addEventListener('click', () => {
      setTimeout(() => {
        // 确保点击动画先执行
        handleClick();
      }, 0);
    });
    

常见误区与最佳实践

误区1:认为 setTimeout(fn, 0) 是最快异步执行方式

// 不推荐 - 宏任务比微任务慢
setTimeout(() => {
  console.log('这会比微任务晚执行');
}, 0);

// 推荐 - 微任务更快执行
queueMicrotask(() => {
  console.log('这会先执行');
});

误区2:微任务中创建大量微任务导致饥饿

function recursiveMicrotask() {
  queueMicrotask(recursiveMicrotask); // 会导致无限循环阻塞主线程
}

最佳实践:合理搭配使用

function fetchWithTimeout(url: string, timeout: number) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('请求超时'));
    }, timeout);
    
    fetch(url)
      .then(response => {
        clearTimeout(timer); // 微任务中清除宏任务
        resolve(response);
      })
      .catch(reject);
  });
}

Node.js 环境差异

在 Node.js 中,任务队列有额外层次:

  1. process.nextTick 队列 (优先级最高)
  2. 微任务队列 (Promise 等)
  3. 宏任务队列 (setTimeout, setImmediate 等)
// Node.js 中的执行顺序示例
console.log('start');

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

console.log('end');

/*
典型输出:
start
end
nextTick
Promise
setTimeout
setImmediate
*/

理解宏任务和微任务的机制,能够帮助开发者编写更高效、更可预测的异步 TypeScript 代码,特别是在处理复杂的异步流程和性能优化时。

相关文章

网友评论

      本文标题:TypeScript 中的 `queueMicrotask` 详

      本文链接:https://www.haomeiwen.com/subject/teysojtx.html