Node.js 定时器与延迟执行
定时器是 Node.js 异步调度的基础,理解其内部机制对性能优化至关重要。
setTimeout 深度解析
JavaScript
// setTimeout 返回 Timeout 对象
const timer = setTimeout(() => {
console.log('执行');
}, 1000);
// Timeout 对象属性
console.log(timer._idleTimeout); // 延迟时间
console.log(timer._onTimeout); // 回调函数
延迟精度问题
JavaScript
// 实际延迟可能超过指定时间
setTimeout(() => {
console.log('实际延迟:', Date.now() - start);
}, 100);
// 原因:
// 1. 事件循环阻塞
// 2. timers 阶段执行时机
// 3. 系统调度精度
延迟范围限制
JavaScript
// 延迟时间范围:1ms - 2147483647ms(约 24.8 天)
setTimeout(() => {}, 0); // 实际最小 1ms
setTimeout(() => {}, 2147483647); // 最大值
setTimeout(() => {}, 2147483648); // 被修正为 1ms
refresh 重置定时器
JavaScript
const timer = setTimeout(() => {
console.log('执行');
}, 1000);
// 重置定时器,重新开始计时
timer.refresh();
// 延迟重置为 1000ms
setInterval 深度解析
JavaScript
// setInterval 执行间隔问题
setInterval(() => {
// 如果回调耗时超过间隔
// 下次执行会延迟
heavyOperation(); // 耗时 2 秒
}, 1000); // 实际间隔变成 2 秒
间隔累积问题
JavaScript
// setInterval 不会累积延迟
setInterval(() => {
console.log('tick');
}, 100);
// 如果某次延迟 500ms
// 只执行一次,不会执行 5 次
动态间隔执行
JavaScript
// 使用 setTimeout 实现动态间隔
function dynamicInterval(fn, delay) {
setTimeout(() => {
fn();
dynamicInterval(fn, delay);
}, delay);
}
// 或在回调中调整延迟
let currentDelay = 1000;
function adaptiveInterval() {
setTimeout(() => {
doWork();
currentDelay = calculateNewDelay();
adaptiveInterval();
}, currentDelay);
}
setImmediate
JavaScript
// setImmediate 在 check 阶段执行
setImmediate(() => {
console.log('immediate');
});
// 与 setTimeout 0 的区别
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
// 在 I/O 回调中: immediate 先于 timeout
setImmediate vs setTimeout
JavaScript
// timers 阶段执行顺序
// setTimeout(fn, 0) → timers 阶段
// setImmediate(fn) → check 阶段
// poll 阶段后的顺序
// poll → check (setImmediate) → timers (setTimeout)
// poll → timers (setTimeout) → check (setImmediate)
// 取决于 poll 阶段计算何时检查 timers
定时器对象操作
unref 自动取消
JavaScript
// unref:允许事件循环退出(无其他活跃句柄时)
const timer = setTimeout(() => {}, 1000);
timer.unref();
// 如果只有这个定时器,事件循环会退出
// 不等待定时器执行
// ref:恢复正常
timer.ref();
hasRef 检查
JavaScript
const timer = setTimeout(() => {}, 1000);
console.log(timer.hasRef()); // true
timer.unref();
console.log(timer.hasRef()); // false
定时器清除
JavaScript
// clearTimeout/clearInterval
const timer1 = setTimeout(() => {}, 1000);
clearTimeout(timer1);
const timer2 = setInterval(() => {}, 1000);
clearInterval(timer2);
// 清除后定时器对象仍存在,但不会执行
console.log(timer1._idleTimeout); // -1(已清除)
定时器队列
JavaScript
// Node.js 使用最小堆管理定时器
// 到期时间最早的定时器最先执行
setTimeout(() => console.log('100ms'), 100);
setTimeout(() => console.log('50ms'), 50);
setTimeout(() => console.log('200ms'), 200);
// 输出顺序: 50ms -> 100ms -> 200ms
定时器精度对比
| 方法 | 最小延迟 | 精度 | 阶段 |
|---|---|---|---|
| setTimeout | 1ms | 事件循环影响 | timers |
| setInterval | 1ms | 回调耗时影响 | timers |
| setImmediate | - | poll 后立即 | check |
| process.nextTick | - | 当前操作后 | - |
性能考量
JavaScript
// ❌ 高频定时器
setInterval(() => {
// 每 1ms 执行,影响性能
}, 1);
// ✅ 合理间隔
setInterval(() => {
// 最低建议 10ms 以上
}, 100);
// ✅ 按需执行
function scheduleNext() {
setTimeout(() => {
if (needWork) {
doWork();
scheduleNext();
}
}, delay);
}
定时器调试
JavaScript
// 查看活跃定时器
process._getActiveHandles().forEach(handle => {
if (handle._idleTimeout > 0) {
console.log('定时器:', handle._idleTimeout);
}
});
// 使用 debug 工具
const asyncHooks = require('async_hooks');
const hook = asyncHooks.createHook({
init(asyncId, type) {
if (type === 'TIMER') {
console.log('定时器创建:', asyncId);
}
}
});
hook.enable();
要点总结
- setTimeout 延迟范围 1ms-24.8天,精度受事件循环影响
- setInterval 回调耗时会影响实际间隔
- setImmediate 在 check 阶段执行,不依赖时间
- unref 定时器不阻止事件循环退出
- 使用 setTimeout 递归替代 setInterval 实现动态间隔
- 高频定时器影响性能,建议最低 10ms 以上
📝 发现内容有误?点击此处直接编辑