Promise 执行顺序规则

59 阅读2分钟

核心事件循环

  • 每一轮事件循环顺序:同步代码 → 清空所有微任务(microtasks)→ 执行一个宏任务(macrotask)→ 重复
  • 微任务:Promise.then/catch/finally、queueMicrotask、MutationObserver
  • 宏任务:setTimeout/setInterval、setImmediate、MessageChannel、I/O

Promise 基本规则

  • executor 同步new Promise(executor) 里的 executor 立即执行
  • then/catch/finally 异步:回调放入微任务队列,当前同步代码结束后再执行
  • 链式 then 顺序:每个 then 产生一个新微任务,按注册顺序 FIFO 执行
  • 返回值影响
    • 返回普通值 → 自动包成 fulfilled Promise,进入下一 then
    • 返回 Promise → 等它落定后再进入下一 then(会“插队等待”,但仍是微任务)
    • 抛错/返回拒绝 → 就近的 catch 处理
  • finally:不接收前值,不改变链状态(除非抛错)

async/await 规则

  • await:把后续代码放进微任务(等待右值 Promise 落定)
  • 等价心智模型await pp.then(...)(但语法更直观)

计时器与微任务

  • 同一轮:先清空微任务再触发计时器回调
  • 计时器内部再排微任务:该微任务会在当前计时器回调结束后、下一个宏任务前立即执行

常见顺序模板

  1. 同步日志
  2. 所有微任务(按注册顺序)
  3. 一个计时器(宏任务)
  4. 该计时器内产生的微任务
  5. 下一个计时器……

典型示例

  • 示例1:基础队列
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1, 4, 3, 2
  • 示例2:then 链与返回 Promise
Promise.resolve().then(() => {
  console.log('A');
  return Promise.resolve('X');
}).then(v => {
  console.log('B', v);
}).then(() => console.log('C'));
// 输出:A, B X, C
  • 示例3:executor 同步 + then 微任务
new Promise(r => {
  console.log('exec');
  r();
}).then(() => console.log('then'));
console.log('end');
// 输出:exec, end, then
  • 示例4:微/宏交替
setTimeout(() => {
  console.log('T1');
  Promise.resolve().then(() => console.log('T1-M'));
});

Promise.resolve().then(() => {
  console.log('M1');
  setTimeout(() => console.log('T2'));
}).then(() => console.log('M2'));
// 输出:M1, M2, T1, T1-M, T2
  • 示例5:注册时机影响顺序
const p = Promise.resolve().then(() => {
  console.log('A');
  return 'X';
});
p.then(v => console.log('B', v));
Promise.resolve().then(() => console.log('C'));
// 输出:A, C, B X

易错点

  • “then 立即执行”误解:then 回调永远是微任务,永远晚于当前同步代码
  • “返回 Promise 会立刻触发下一个 then”误解:要等返回的 Promise 落定
  • 计时器先于微任务:错误。微任务总是先于宏任务
  • finally 会改变值/状态:错误。它只透传(除非内部抛错)

快速答题口诀

  • 先同步,后微任,再宏任
  • then 都是微任务,按注册顺序
  • 返回 Promise 先等落定再继续
  • await 就是把后续代码放进微任务