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; }));
};
}
实际应用场景
- 性能敏感的操作:当需要尽快执行某些操作,但又不想阻塞当前任务时
- 状态批量更新:在 UI 框架中批量处理状态变更
- Promise 的替代:当不需要 Promise 的完整功能时,更轻量级的方案
- 测试工具:在测试中控制微任务队列
注意事项
-
避免无限递归:
function dangerous() { queueMicrotask(dangerous); // 这将导致无限微任务循环 } -
错误处理:
queueMicrotask(() => { try { // 可能出错的代码 } catch (error) { console.error('微任务出错:', error); } }); -
性能考量:虽然微任务比宏任务快,但大量微任务仍会导致主线程阻塞
-
执行顺序:微任务会在当前宏任务结束前执行,但不同来源的微任务可能有不同的排队顺序
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 -
queueMicrotaskAPI - 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
*/
实际应用场景
微任务适用场景
-
状态批量更新:在 UI 框架中合并多次状态变更
let needsUpdate = false; function scheduleUpdate() { if (!needsUpdate) { needsUpdate = true; queueMicrotask(() => { needsUpdate = false; performUpdate(); }); } } -
Promise 链式调用优化
function fetchData() { return fetch('/api/data') .then(response => response.json()) .then(data => processData(data)); // 微任务队列处理 }
宏任务适用场景
-
长时间运行的任务分片
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(); } -
用户交互延迟处理
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 中,任务队列有额外层次:
-
process.nextTick队列 (优先级最高) - 微任务队列 (Promise 等)
- 宏任务队列 (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 代码,特别是在处理复杂的异步流程和性能优化时。







网友评论