第 26 章

Promise:PromiseCapability 与解析算法

Promise 不是语法糖,它是一套完整的状态机协议。当你调用 resolve(thenable) 时触发的额外微任务、.then 返回的新 Promise 为什么与原 Promise 无关——这些行为都源自规范定义的精确算法,不理解算法就无法预测行为。

🔹 Level 1 · 你需要知道的

Promise 的三个状态

         pending
        /       \
  fulfilled    rejected

状态只能从 pending 转移到 fulfilled 或 rejected,且只能转移一次(settled 之后不可变)。

核心 API 速查

// 创建
const p = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => resolve('done'), 1000);
});

// 链式调用
p.then(value => '处理成功')   // 返回新 Promise
 .catch(err => '处理错误')    // 等价于 .then(null, onRejected)
 .finally(() => '清理资源');  // 不改变传递的值/原因

// 静态方法
Promise.resolve(value)         // 直接创建 fulfilled Promise
Promise.reject(reason)         // 直接创建 rejected Promise

四个组合方法对比

方法 成功条件 失败条件 返回值
Promise.all(iterable) 全部 fulfilled 任一 rejected fulfilled 值数组(顺序同输入)
Promise.race(iterable) 最先 settled 的结果 同左 最先 settled 的值或原因
Promise.allSettled(iterable) 全部 settled(无论成败) 永不 reject {status, value/reason} 数组
Promise.any(iterable) 任一 fulfilled 全部 rejected 最先 fulfilled 的值

链式调用的值传递规则

Promise.resolve(1)
  .then(v => v + 1)           // v = 1,返回 2
  .then(v => {                // v = 2
    throw new Error('oops');  // 抛出异常 → rejected
  })
  .then(v => v * 10)          // 跳过(上游 rejected)
  .catch(e => e.message)      // 捕获,返回 'oops' → fulfilled
  .then(v => console.log(v)); // v = 'oops'

关键点:


🔸 Level 2 · 它是怎么运行的

Promise 的内部插槽

每个 Promise 对象维护以下内部状态(规范用双括号表示内部插槽):

Promise 对象内部结构:
┌────────────────────────────────────────────────┐
│  [[PromiseState]]                              │
│    'pending' | 'fulfilled' | 'rejected'        │
│                                                │
│  [[PromiseResult]]                             │
│    fulfilled 时:解决值(any ECMAScript value) │
│    rejected 时:拒绝原因(any ECMAScript value)│
│    pending 时:undefined                        │
│                                                │
│  [[PromiseFulfillReactions]]                   │
│    List of PromiseReaction records             │
│    (pending 时通过 .then 注册的成功回调)       │
│                                                │
│  [[PromiseRejectReactions]]                    │
│    List of PromiseReaction records             │
│    (pending 时通过 .catch/.then 注册的失败回调)│
│                                                │
│  [[PromiseIsHandled]]                          │
│    Boolean,是否有 rejection handler           │
│    (用于 unhandledRejection 检测)             │
└────────────────────────────────────────────────┘

resolve 函数的执行流程

new Promise((resolve, reject) => {...}) 中的 resolve 函数,其完整执行逻辑:

┌──────────────────────────────────────────────────────────────┐
│  Promise Resolve Function 执行流程                            │
│                                                              │
│  调用 resolve(value)                                         │
│        │                                                     │
│        ▼                                                     │
│  [[PromiseState]] 是 'pending'?                             │
│        │ 否 → 直接 return(幂等:已 settled 的 Promise        │
│        │      再次 resolve/reject 无效)                      │
│        │ 是 ↓                                                │
│        ▼                                                     │
│  value === this Promise?                                    │
│        │ 是 → reject(TypeError: Chaining cycle)             │
│        │ 否 ↓                                                │
│        ▼                                                     │
│  IsCallable(value.then)?(value 是 thenable?)             │
│        │ 是 → EnqueueJob(NewPromiseResolveThenableJob,       │
│        │         [promise, value, then])                     │
│        │      (注意:这里产生一个额外微任务)                  │
│        │ 否 ↓                                                │
│        ▼                                                     │
│  设置 [[PromiseState]] = 'fulfilled'                         │
│  设置 [[PromiseResult]] = value                              │
│  TriggerPromiseReactions(fulfillReactions, value)            │
│  (将所有 fulfillReactions 的 job 推入 PromiseJobs 队列)    │
└──────────────────────────────────────────────────────────────┘

为什么 resolve(thenable) 产生额外微任务

const p1 = Promise.resolve(42);  // 已 fulfilled 的 Promise

// 情况1:直接传值
new Promise(resolve => resolve(42))
  .then(v => console.log('A', v));  // 1个微任务后执行

// 情况2:传入 thenable(包括 Promise)
new Promise(resolve => resolve(p1))  // resolve 收到一个 thenable!
  .then(v => console.log('B', v));   // 2个微任务后执行

内部发生了什么:

情况2的微任务队列演变:

Step 1(同步结束后):
  微任务队列 = [NewPromiseResolveThenableJob(p_outer, p1)]

Step 2(执行 NewPromiseResolveThenableJob):
  调用 p1.then(resolve_outer, reject_outer)
  因为 p1 已 fulfilled,立即注册微任务:
  微任务队列 = [PromiseReactionJob(resolve_outer, 42)]

Step 3(执行 PromiseReactionJob):
  调用 resolve_outer(42),p_outer 变为 fulfilled
  注册 .then 回调的微任务:
  微任务队列 = [PromiseReactionJob(B_callback, 42)]

Step 4(执行 B_callback):
  输出 'B 42'

相比情况1(只有步骤3和4),情况2多了2个微任务层级

.then 的内部实现

// 规范中 PerformPromiseThen 的逻辑(简化)
Promise.prototype.then = function(onFulfilled, onRejected) {
  // 1. 创建新 Promise(使用 SpeciesConstructor)
  const C = SpeciesConstructor(this, Promise);
  const resultCapability = NewPromiseCapability(C);

  // 2. 标准化回调(非函数时用透传函数代替)
  const fulfillReaction = new PromiseReaction(
    resultCapability,
    'Fulfill',
    IsCallable(onFulfilled) ? onFulfilled : undefined
  );
  const rejectReaction = new PromiseReaction(
    resultCapability,
    'Reject',
    IsCallable(onRejected) ? onRejected : undefined
  );

  // 3. 根据当前状态决定行为
  if (this.[[PromiseState]] === 'pending') {
    // 还没 settled:加入等待队列
    this.[[PromiseFulfillReactions]].push(fulfillReaction);
    this.[[PromiseRejectReactions]].push(rejectReaction);
  } else if (this.[[PromiseState]] === 'fulfilled') {
    // 已 fulfilled:立即入队微任务
    EnqueueJob('PromiseJobs', PromiseReactionJob, [fulfillReaction, this.[[PromiseResult]]]);
  } else {
    // 已 rejected:立即入队微任务
    EnqueueJob('PromiseJobs', PromiseReactionJob, [rejectReaction, this.[[PromiseResult]]]);
  }

  // 4. 返回新 Promise
  return resultCapability.[[Promise]];
};

关键点: .then 总是返回一个新的 Promise(通过 NewPromiseCapability 创建)。原 Promise 与新 Promise 之间通过 PromiseReaction 连接:原 Promise 的结果会触发 reaction,reaction 的执行结果会 resolve/reject 新 Promise。

PromiseCapability Record

PromiseCapability 是规范中用来追踪一个 Promise 及其配套的 resolve/reject 函数的记录结构:

PromiseCapability Record:
  [[Promise]]  — 关联的 Promise 对象
  [[Resolve]]  — 解析函数(调用它使 Promise fulfilled)
  [[Reject]]   — 拒绝函数(调用它使 Promise rejected)

NewPromiseCapability(C) 的工作:

  1. 调用 new C(executor),executor 捕获 resolve 和 reject 函数
  2. 返回 {[[Promise]]: p, [[Resolve]]: resolve, [[Reject]]: reject}

这是 Promise.all 等静态方法能控制一个从外部创建的 Promise 的机制。

Promise 链的完整数据流

resolve('done')
     │
     ▼
Promise P1 [fulfilled: 'done']
     │
     │ .then(v => v.toUpperCase())
     ▼
PromiseReactionJob 执行,调用 callback('done')
     │
     ▼
callback 返回 'DONE'
     │
     ▼
resultCapability.[[Resolve]]('DONE')
     │
     ▼
Promise P2 [fulfilled: 'DONE']
     │
     │ .then(v => console.log(v))
     ▼
输出 'DONE'

🔺 Level 3 · 规范怎么定义的

规范 §27.2:Promise Objects

ECMAScript 2024 规范第27.2节定义了 Promise 对象。以下是核心算法的规范原文摘录:

Promise Resolve Functions(§27.2.1.3.2):

  1. Let F be the active function object.
  2. Assert: F has a [[Promise]] internal slot whose value is an Object.
  3. Let promise be F.[[Promise]].
  4. Let alreadyResolved be F.[[AlreadyResolved]].
  5. If alreadyResolved.[[Value]] is true, return undefined.
  6. Set alreadyResolved.[[Value]] to true.
  7. If SameValue(resolution, promise) is true: a. Let selfResolutionError be a newly created TypeError object. b. Perform RejectPromise(promise, selfResolutionError). c. Return undefined.
  8. If resolution is not an Object, then a. Perform FulfillPromise(promise, resolution). b. Return undefined.
  9. Let then be Completion(Get(resolution, "then")).
  10. If then is an abrupt completion, then a. Perform RejectPromise(promise, then.[[Value]]). b. Return undefined.
  11. Let thenAction be then.[[Value]].
  12. If IsCallable(thenAction) is false: a. Perform FulfillPromise(promise, resolution). b. Return undefined.
  13. Let thenJobCallback be HostMakeJobCallback(thenAction).
  14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
  15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
  16. Return undefined.

FulfillPromise(§27.2.1.4):

  1. Assert: The value of promise.[[PromiseState]] is pending.
  2. Let reactions be promise.[[PromiseFulfillReactions]].
  3. Set promise.[[PromiseResult]] to value.
  4. Set promise.[[PromiseFulfillReactions]] to undefined.
  5. Set promise.[[PromiseRejectReactions]] to undefined.
  6. Set promise.[[PromiseState]] to fulfilled.
  7. Perform TriggerPromiseReactions(reactions, value).
  8. Return unused.

PerformPromiseThen(§27.2.5.4.1).then 方法的核心实现,处理:

NewPromiseCapability(§27.2.1.5):

NewPromiseCapability(C):
  1. 创建一个新的 PromiseCapability Record
  2. 创建 executor 函数,捕获 resolve 和 reject
  3. 调用 new C(executor),将结果存入 capability.[[Promise]]
  4. 断言 capability.[[Resolve]] 是 callable
  5. 断言 capability.[[Reject]] 是 callable
  6. 返回 capability

unhandledRejection 的触发机制

规范定义了 HostPromiseRejectionTracker 抽象操作,宿主环境(浏览器/Node.js)实现它来跟踪未处理的 rejection:

HostPromiseRejectionTracker(promise, operation):
  operation 可以是:
    'reject'  — Promise 被 rejected(此时可能还没有 handler)
    'handle'  — Promise 的 rejection 被处理(添加了 .catch 等)

浏览器的实现策略:

  1. Promise rejected 时(operation = 'reject'):将 Promise 加入"待检查"列表
  2. 在当前 microtask checkpoint 结束时:检查列表中哪些 Promise 仍然没有 [[PromiseIsHandled]] = true
  3. 对未处理的 rejection 触发 unhandledrejection 事件

这解释了为什么以下代码不会触发 unhandledRejection

const p = Promise.reject(new Error('oops'));
// 此时 p 是 rejected 且无 handler

setTimeout(() => {
  p.catch(e => console.log('caught'));
  // 虽然是异步添加 catch,但 unhandledRejection 
  // 在当前同步代码 + 微任务都结束后才检查
}, 0);
// 在浏览器中:0ms 的宏任务在 unhandledRejection 检查之前执行
// 所以可能看到也可能看不到 unhandledRejection(实现相关)

实际上,浏览器通常在当前 task 的微任务 checkpoint 结束后才检查,如果 setTimeout 足够快(如 0ms),有些情况下 rejection 已被处理,不会触发警告。


💎 Level 4 · 边界与陷阱

陷阱1:Promise.resolve(aPromise) 的身份保留

const p1 = new Promise(resolve => resolve(42));
const p2 = Promise.resolve(p1);

console.log(p1 === p2);  // true!

// 但是:
class MyPromise extends Promise {}
const mp = new MyPromise(resolve => resolve(42));
const p3 = Promise.resolve(mp);

console.log(mp === p3);  // false!
// Promise.resolve 检查 mp 的构造函数是否是 Promise,
// mp 是 MyPromise 实例,构造函数不是 Promise,所以创建新 Promise

规范行为: Promise.resolve(x)x 是 Promise 且 x.constructor === Promise 时直接返回 x。这个优化避免了不必要的 Promise 包装。

陷阱2:resolve(thenable) 的多余微任务详解

// 以下两者输出顺序不同:

// 代码 A:resolve 原始值
let resolveA;
const pA = new Promise(r => { resolveA = r; });
pA.then(() => console.log('A done'));
resolveA(42);  // 直接 fulfill,1个微任务后执行回调
Promise.resolve().then(() => console.log('plain'));

// 代码 B:resolve 一个已 fulfilled 的 Promise
let resolveB;
const pB = new Promise(r => { resolveB = r; });
pB.then(() => console.log('B done'));
resolveB(Promise.resolve(42));  // 通过 thenable,2个额外微任务
Promise.resolve().then(() => console.log('plain'));

// 代码 A 输出:A done → plain
// 代码 B 输出:plain → B done

这个差异在写并发控制代码时会导致意外的执行顺序。实践建议:如果知道值不是 thenable,直接 resolve(value) 而非 resolve(Promise.resolve(value))

陷阱3:.then(null, onRejected) 与 .catch(onRejected) 的细微差别

// 看似相同,实则有一个差别:
promise
  .then(onFulfilled, onRejected);  // 形式1

promise
  .then(onFulfilled)
  .catch(onRejected);             // 形式2

// 差别在于:onFulfilled 抛出错误时:
// 形式1:onRejected 不会捕获 onFulfilled 抛出的错误
//        (因为是同一个 .then,onFulfilled 抛出的错误直接被下游传递)
// 形式2:.catch 可以捕获 onFulfilled 抛出的错误

function riskyFulfill(v) {
  throw new Error('fulfilled handler failed');
}

Promise.resolve(1)
  .then(riskyFulfill, err => console.log('caught in then:', err.message));
// 不会捕获,错误传递到下游
// 因为 onRejected 只处理 Promise 本身的 reject,不处理 onFulfilled 的异常

Promise.resolve(1)
  .then(riskyFulfill)
  .catch(err => console.log('caught in catch:', err.message));
// 可以捕获:'caught in catch: fulfilled handler failed'

陷阱4:Promise.all 的短路行为与内存泄漏

async function fetchAll() {
  const results = await Promise.all([
    fetch('/api/1'),  // 成功
    fetch('/api/2'),  // 假设这个 reject
    fetch('/api/3'),  // 成功,但结果被忽略
  ]);
  return results;
}

关键事实:

  1. Promise.all 的 reject 是短路的——任一 Promise reject 后,返回的 Promise 立即 reject
  2. 但其他两个 fetch 仍在执行,只是结果被忽略
  3. 如果 fetch 有副作用(写入、锁定资源),这些副作用仍会发生
  4. 这不是内存泄漏,但可能是逻辑错误

更健壮的写法:

// 使用 AbortController 真正取消请求
const controller = new AbortController();
try {
  const results = await Promise.all([
    fetch('/api/1', { signal: controller.signal }),
    fetch('/api/2', { signal: controller.signal }),
    fetch('/api/3', { signal: controller.signal }),
  ]);
} catch (e) {
  controller.abort();  // 取消所有未完成请求
  throw e;
}

陷阱5:Promise 构造函数的 executor 是同步执行的

console.log('before');
new Promise((resolve) => {
  console.log('executor');  // 同步执行!
  resolve(1);               // 同步调用!
  console.log('after resolve');  // 也是同步执行!
});
console.log('after');

// 输出:before → executor → after resolve → after
// .then 的回调是微任务(异步),但 executor 本身是同步的

这意味着在 executor 里执行耗时操作(如大量计算)会阻塞主线程,与 Promise 的"异步"表象不符。Promise 的异步性来自 .then 回调通过微任务调度,不是 executor 本身。

小结

  1. Promise 内部有 5 个关键插槽:[[PromiseState]][[PromiseResult]][[PromiseFulfillReactions]][[PromiseRejectReactions]][[PromiseIsHandled]]
  2. resolve(thenable)resolve(value) 多触发 NewPromiseResolveThenableJob,即额外2个微任务层级,执行顺序会滞后。
  3. Promise.resolve(p)p 是同构造函数的 Promise 时,直接返回 p,不创建新对象。
  4. .then(onFulfilled, onRejected) 中的 onRejected 不捕获 onFulfilled 抛出的错误;.catch 会。
  5. Promise.all 短路时其他 Promise 仍在执行,需要 AbortController 才能真正取消。
本章评分
4.6  / 5  (4 评分)

💬 留言讨论