Promise:PromiseCapability 与解析算法
Promise 不是语法糖,它是一套完整的状态机协议。当你调用 resolve(thenable) 时触发的额外微任务、.then 返回的新 Promise 为什么与原 Promise 无关——这些行为都源自规范定义的精确算法,不理解算法就无法预测行为。
🔹 Level 1 · 你需要知道的
Promise 的三个状态
pending
/ \
fulfilled rejected
- pending:初始状态,既未 fulfilled 也未 rejected
- fulfilled:操作成功完成,具有一个不可变的值(value)
- rejected:操作失败,具有一个不可变的原因(reason)
状态只能从 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'
关键点:
.then的回调返回值成为下一个 Promise 的 fulfilled 值- 抛出异常等价于返回一个 rejected Promise
.catch捕获后返回正常值,链路恢复为 fulfilled
🔸 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) 的工作:
- 调用
new C(executor),executor 捕获 resolve 和 reject 函数 - 返回
{[[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):
- Let F be the active function object.
- Assert: F has a [[Promise]] internal slot whose value is an Object.
- Let promise be F.[[Promise]].
- Let alreadyResolved be F.[[AlreadyResolved]].
- If alreadyResolved.[[Value]] is true, return undefined.
- Set alreadyResolved.[[Value]] to true.
- If SameValue(resolution, promise) is true: a. Let selfResolutionError be a newly created TypeError object. b. Perform RejectPromise(promise, selfResolutionError). c. Return undefined.
- If resolution is not an Object, then a. Perform FulfillPromise(promise, resolution). b. Return undefined.
- Let then be Completion(Get(resolution, "then")).
- If then is an abrupt completion, then a. Perform RejectPromise(promise, then.[[Value]]). b. Return undefined.
- Let thenAction be then.[[Value]].
- If IsCallable(thenAction) is false: a. Perform FulfillPromise(promise, resolution). b. Return undefined.
- Let thenJobCallback be HostMakeJobCallback(thenAction).
- Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
- Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
- Return undefined.
FulfillPromise(§27.2.1.4):
- Assert: The value of promise.[[PromiseState]] is pending.
- Let reactions be promise.[[PromiseFulfillReactions]].
- Set promise.[[PromiseResult]] to value.
- Set promise.[[PromiseFulfillReactions]] to undefined.
- Set promise.[[PromiseRejectReactions]] to undefined.
- Set promise.[[PromiseState]] to fulfilled.
- Perform TriggerPromiseReactions(reactions, value).
- Return unused.
PerformPromiseThen(§27.2.5.4.1) 是 .then 方法的核心实现,处理:
- 创建 fulfillReaction 和 rejectReaction
- 若 Promise 已 settled,立即 EnqueueJob;否则加入对应 reactions 列表
- 返回通过 resultCapability 创建的新 Promise
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 等)
浏览器的实现策略:
- Promise rejected 时(
operation = 'reject'):将 Promise 加入"待检查"列表 - 在当前 microtask checkpoint 结束时:检查列表中哪些 Promise 仍然没有
[[PromiseIsHandled]] = true - 对未处理的 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;
}
关键事实:
- Promise.all 的 reject 是短路的——任一 Promise reject 后,返回的 Promise 立即 reject
- 但其他两个
fetch仍在执行,只是结果被忽略 - 如果 fetch 有副作用(写入、锁定资源),这些副作用仍会发生
- 这不是内存泄漏,但可能是逻辑错误
更健壮的写法:
// 使用 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 本身。
小结
- Promise 内部有 5 个关键插槽:
[[PromiseState]]、[[PromiseResult]]、[[PromiseFulfillReactions]]、[[PromiseRejectReactions]]、[[PromiseIsHandled]]。 resolve(thenable)比resolve(value)多触发NewPromiseResolveThenableJob,即额外2个微任务层级,执行顺序会滞后。Promise.resolve(p)当p是同构造函数的 Promise 时,直接返回 p,不创建新对象。.then(onFulfilled, onRejected)中的onRejected不捕获onFulfilled抛出的错误;.catch会。- Promise.all 短路时其他 Promise 仍在执行,需要 AbortController 才能真正取消。