前端面试 P028:异步输出顺序的逐步推演
Showing the original in Chinese (China)
来源:方格前端讲面试 · 28. “异步题大汇总”:输出顺序、陷阱解析,17 分 17 秒。
回看定位:02:58 任务分类 · 07:18 await · 08:14 Node 的原说法 · 09:14 逐步分析
先标注环境,再记四份信息
记录同步输出、当前任务、微任务队列、待执行定时回调。只在相关 Promise 真正具备运行条件时,才将对应反应纳入推演;链上的所有 then 不是一开始就全部执行或全部就绪。
console.log('start');
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(() => console.log('promise in timer'));
}, 0);
Promise.resolve()
.then(() => {
console.log('promise1');
setTimeout(() => console.log('timer in promise'), 0);
})
.then(() => console.log('promise2'));
console.log('end');
// 常见浏览器输出:
// start、end、promise1、promise2、timer1、promise in timer、timer in promise
先完成 start、end;检查微任务时执行 promise1,它注册后面的 timer,并使下一段 then 之后有机会运行;随后 promise2。第一个 timer 运行后,还要检查它产生的微任务,不能先把所有 timer 全执行完。
需要纠正的 Node.js 说法
视频说 Node.js 11 以后 nextTick 与 Promise 已在同一个队列,这不正确。nextTick 仍有独立机制;ES 模块入口也会影响某些示例的顺序。
setImmediate 与 setTimeout(0) 也不是任何位置都有固定先后。IO 回调中常见的顺序,与主模块顶层调用的条件不同,Node.js/libuv 版本同样有影响。
输出题不能替代完整宿主模型
“任务后排空微任务”是入门推演关键,但浏览器不只有一条普通 FIFO 宏任务队列;渲染也不应直接当成普通宏任务。本例的定时器来自同一类场景,因此适合按这里的条件分析。
微任务执行过程中产生的微任务仍会在本次检查继续处理;递归添加微任务可能造成饥饿。遇到 await 普通值也不能当作同步继续,见 前端面试 P023:async、await、异步恢复与并发。