先记住这个答案
setTimeout 表达经过一定时间阈值后再安排回调,适合重试等待、超时检查或延迟任务,但不保证精确执行时刻。setImmediate 更适合把一段后续工作安排到事件循环后续的 check 机会,例如拆分较长计算,让当前回调先结束。两者都不会创建新的计算线程,也不能抢占正在执行的 JavaScript。真正的业务先后依赖应直接串联,CPU 并行需求应考虑工作线程或独立任务,而不是用调度函数的名字推断更快或更强的执行保证。
- 有时间阈值需求时使用定时器语义
- 拆分当前工作可考虑 setImmediate
- 调度到以后执行不等于换线程并行
延迟任务需要围绕时间条件设计
请求重试希望至少等待一段间隔,可以用定时器安排下一次尝试,但循环忙碌时实际执行会更晚。严格截止判断应在恢复执行后重新比较时间,而不是假设定时器回调一定恰好在截止点运行。
周期任务还需要考虑执行耗时、漂移和是否允许重叠。选择 setTimeout 并不能自动决定这些业务规则,应明确下一次时间是基于上次开始、结束还是固定时间线,再安排对应调度。
计算切片需要结束当前同步片段
把下一片工作交给 setImmediate,可以让当前回调返回,为其他事件提供处理机会。效果依赖每片足够短;如果每一片仍做数秒计算,只是把长阻塞分成了几个较大的阻塞。
await 一个已经完成的 Promise 通常只进入微任务续体,不等于给 I/O 完整让出循环机会。不断追加 nextTick 或微任务也可能造成饥饿,因此切片时要明确选择哪种调度边界,而不是只追求语法上出现 await。
调度工具不能代替任务容量与依赖设计
如果慢点来自真正大量 CPU 计算,切片可能改善响应性,却不减少总计算量。需要并行或隔离时,应评估工作线程、独立进程或外部作业系统,并计算数据传输、排队和启动成本。
两个操作必须有先后关系时,直接在前一个完成后执行后一个更清晰。不要同时安排 timer 与 immediate,再依赖某个上下文里谁先执行维持正确性,重构注册位置后这种隐含关系很容易失效。
容易答错的地方
- 认为 setImmediate 一定比零延时定时器更早
- 相对顺序需要结合执行上下文,不能从名字推出全局优先级。选择接口应围绕时间阈值和让出机会的需求,真正依赖顺序时要显式串联,而不是靠两个队列竞速。
- 用 Promise.resolve 包装就认为计算不阻塞了
- 同步计算仍然运行在当前线程,微任务也可能连续占据执行机会。应测量单片耗时并选择合适调度边界,需要并行时使用明确执行资源,不能把异步语法当作自动卸载。
面试官还会怎么问?
setImmediate 适合实现精确定时任务吗?
不适合用它表达具体延时阈值,它安排的是后续调度机会。即使使用定时器也不具备硬实时保证,重要任务应在执行时核对实际时间,并设计超时、补偿和重复触发规则。
在 setImmediate 回调里再安排一个 immediate 会怎样?
新安排的 immediate 会留到后续循环机会,而不是无限递归地在当前调用栈执行。这个性质可用于分片,但每个片段仍需有界,且取消、错误和最终结果需要由业务明确管理。
什么时候应该直接用工作线程?
当 CPU 工作足够大、可独立执行且传输成本可接受时,工作线程可能更合适。还要限制线程数量、排队和任务取消,避免每个请求都新建执行资源,不能只看主线程日志是否变少。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。