Promise构造器
深入解析 Promise 构造器的形式化语义、Promise A+ 规范、状态机模型、thenable 鸭子类型、微任务调度、并发原语(all/race/allSettled/any)、Promise.withResolvers 等高级主题,涵盖 MIT 6.S081 / Stanford CS107 级别的工程实践
Promise 构造器
本文是 FANDEX JavaScript 模块的核心理论文档之一,定位为 MIT 6.S081 / Stanford CS107 / CMU 15-410 级别的工程教学材料,涵盖 Promise A+ 规范、状态机形式化、thenable 鸭子类型、微任务调度、并发原语、Promise.withResolvers 等高级主题。
0. 学习导览
0.1 学习路径
回调地狱 → Promise 历史 → Promise A+ 规范 → 三种状态 → 构造器签名
→ then/catch/finally → thenable 解析 → 微任务调度 → Promise.withResolvers
→ all/race/allSettled/any → 错误处理链 → 常见陷阱 → 性能优化 → 测试 → 案例
0.2 前置知识
- 熟悉 JavaScript 函数与闭包
- 理解事件循环(Event Loop)与宏/微任务
- 了解异步编程的需求(网络请求、定时器、I/O)
- 掌握基本的对象模型与原型链
0.3 阅读建议
- 第一遍:基础语法、三种状态、then/catch/finally
- 第二遍:thenable 解析过程与微任务调度
- 第三遍:并发原语与生产级模式
- 实战:按习题顺序实现自定义 Promise 与并发控制器
1. 历史动机与技术演进
1.1 回调地狱:Promise 出现前的痛点
在 Promise 标准化之前,JavaScript 异步编程主要依赖回调函数。多层嵌套的回调形成了著名的”回调地狱”(Callback Hell):
// 典型的回调地狱
getUser(userId, function (err, user) {
if (err) return handleError(err);
getOrders(user.id, function (err, orders) {
if (err) return handleError(err);
getOrderDetail(orders[0].id, function (err, detail) {
if (err) return handleError(err);
getProducts(detail.productIds, function (err, products) {
if (err) return handleError(err);
// ...
});
});
});
});
回调地狱的核心问题包括:
- 横向增长:嵌套层次越深,代码越往右缩进,可读性骤降
- 错误处理分散:每层都需手动检查 err,极易遗漏
- 无法组合:多个并行回调的同步、竞速、聚合困难
- 控制流倒置:程序流程被回调的调用方掌控,而非程序员掌控
- 信任问题:回调可能被多次调用、不被调用、被同步调用或异步调用
1.2 Promise 的概念萌芽
Promise 概念并非 JavaScript 独创。它的思想可追溯至:
- 1976 年:Daniel Friedman 和 David Wise 在 The Impact of Applicative Programming on Multiprocessing 中提出 “future” 概念
- 1980 年代:Lisp 与 Multilisp 中的 “promise” 与 “future” 用于并行计算
- 1989 年:Barbara Liskov 与 Liuba Shrira 在 Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems 中正式定义 “promise”
- 2007 年:E 语言(由 Mark S. Miller 设计)引入 “Vow” 与 “Ref” 抽象,成为 Promise A+ 的设计灵感
- 2009 年:Kris Kowal 在 CommonJS 提出 Promises/A 提案,Q 库实现
- 2011 年:Domenic Denicola 等人发起 Promises/A+ 规范,统一各类 Promise 实现的行为
- 2015 年:ES6(ES2015)将 Promise 纳入语言标准
1.3 Promise A+ 规范的诞生
Promise A+ 规范(Promises/A+ Specification)是 JavaScript 生态中最重要的异步规范之一。它的目标是:
“An open standard for sound, interoperable JavaScript promises—that is, conforming implementations can interoperate within a common promise ecosystem.”
(一个健全、可互操作的 JavaScript Promise 开放标准——符合规范的实现可以在共同的 Promise 生态中互操作。)
A+ 规范的核心贡献:
- 状态机定义:明确 pending/fulfilled/rejected 三态与单次转换
- then 接口:规定 then 方法接收两个参数(onFulfilled, onRejected)
- 解析过程:定义 Promise Resolution Procedure,处理 thenable 鸭子类型
- 异步执行:要求 onFulfilled/onRejected 必须异步执行
- 链式调用:then 返回新 Promise,支持值穿透与错误冒泡
1.4 关键人物与里程碑
- Mark S. Miller:Secure ECMAScript 作者,E 语言 Vow 设计者,Promise A+ 顾问
- Kris Kowal:Q 库作者,CommonJS Promises/A 提案人
- Domenic Denicola:Promise A+ 规范编辑,Chakra/Google 工程师,推动 Promise 进入 ES6
- Brian Terlson:Microsoft 工程师,TC39 编辑,推动 async/await 进入 ES2017
| 年份 | 事件 |
|---|---|
| 1976 | Friedman & Wise 提出 “future” 概念 |
| 1989 | Liskov & Shrira 在 PARCOM 88 论文定义 “promise” |
| 2007 | Mark Miller 在 E 语言中实现 Vow/Ref 抽象 |
| 2009 | Kris Kowal 在 CommonJS 提出 Promises/A |
| 2011 | Domenic Denicola 等人发起 Promise A+ 规范 |
| 2012 | Q.js、when.js、rsvp.js 等 A+ 兼容实现流行 |
| 2015 | ES6(ES2015)正式纳入 Promise,所有主流浏览器原生支持 |
| 2017 | ES2017 引入 async/await,基于 Promise 构建 |
| 2020 | ES2020 引入 Promise.allSettled |
| 2021 | ES2021 引入 Promise.any 与 AggregateError |
| 2024 | ES2024 引入 Promise.withResolvers,简化 resolve/reject 提取 |
| 2025 | ES2025 持续完善 Promise,Iterator Helpers 与 Promise 协同 |
1.5 Promise 解决的核心问题
- 回调地狱 → 链式调用:then 返回新 Promise,支持线性异步流
- 错误分散 → 集中处理:catch 捕获链上任一节点的错误
- 无法组合 → 并发原语:all/race/allSettled/any 表达并行模式
- 控制流倒置 → 主动控制:程序员用 then 决定下一步,而非回调被调用
- 信任问题 → 规范保证:Promise A+ 担保单次调用、异步执行、状态不可逆
2. 形式化定义
2.1 Promise 的状态机模型
Promise 可形式化为一个三态有限状态机(FSM):
- 状态集
- 初始状态
- 事件集
- 转移函数 :
- 输出函数 :
2.2 单次转换公理
Promise A+ 规范的核心公理是单次转换(One-shot):
一旦 Promise 在 时刻离开 pending,其状态永久保持不变。这与 EventEmitter 的”多次触发”模型形成鲜明对比:
| 性质 | Promise | EventEmitter |
|---|---|---|
| 状态 | 三态 FSM | 无显式状态 |
| 触发次数 | 单次(once) | 多次 |
| 错误传播 | 自动沿链冒泡 | 需手动 emit(‘error’) |
| 组合性 | 高(then 返回 Promise) | 低(回调集合) |
| 异步保证 | 微任务调度 | 同步或异步均可 |
2.3 then 的代数语义
then 方法可形式化为:
即:接收一个 Promise(A 类型)、一个成功回调与一个失败回调,返回一个新的 Promise(B 类型)。这是函数式编程中 Monad 的 bind 操作(在 Haskell 中称为 >>=,在 Scala 中称为 flatMap)的实例。
Promise 作为 Monad 的接口:
- return(pure/resolve):,即
Promise.resolve(a) - bind(flatMap/then):
Monad 三定律在 Promise 上的体现:
- 左单位律:
Promise.resolve(a).then(f) ≡ f(a) - 右单位律:
p.then(x => Promise.resolve(x)) ≡ p - 结合律:
p.then(f).then(g) ≡ p.then(x => f(x).then(g))
2.4 微任务调度模型
ECMAScript 规范定义了 PromiseJob 队列,作为微任务(Microtask)的实现机制。then 的回调以 PromiseJob 形式入队,在当前 Job 执行结束后立即处理:
调度时序的形式化:
即:then 注册回调 → 当前同步代码执行完 → 微任务队列清空 → 下一个宏任务。
2.5 解析过程(Resolution Procedure)
Promise A+ 规范第 2.3 节定义了 Promise Resolution Procedure,记为 [[Resolve]](promise, x):
解析过程的判定树:
- 若 :拒绝(TypeError: chaining cycle)
- 若 是 Promise:采用 的状态
- 若 是 thenable(对象或函数,有 then 方法):
- 尝试调用
- 防止多次调用(called flag)
- 防止 then 访问抛错(try-catch)
- 若 是普通值:fulfill(promise, x)
3. Promise 构造器基础
3.1 构造器签名
new Promise(executor)
- executor:函数,形式为
(resolve, reject) => void - resolve:函数,形式为
(value) => void,将 Promise 状态从 pending 转为 fulfilled - reject:函数,形式为
(reason) => void,将 Promise 状态从 pending 转为 rejected - 返回值:新的 Promise 实例
3.2 执行器的同步性
executor 在构造器调用时同步执行:
console.log('1. before');
const p = new Promise((resolve, reject) => {
console.log('2. inside executor');
resolve('hello');
});
console.log('3. after');
p.then((v) => console.log('4. then:', v));
console.log('5. end');
// 输出顺序:1, 2, 3, 5, 4
// 解析:executor 同步执行(2 在 3 之前),then 回调异步执行(4 在 5 之后)
3.3 最小示例
// 同步 resolve
const p1 = new Promise((resolve) => resolve(42));
p1.then((v) => console.log(v)); // 42
// 异步 resolve
const p2 = new Promise((resolve) => {
setTimeout(() => resolve('async'), 100);
});
p2.then((v) => console.log(v)); // 'async' (100ms 后)
// reject
const p3 = new Promise((_, reject) => reject(new Error('failed')));
p3.catch((e) => console.log(e.message)); // 'failed'
// executor 抛错 → 自动 reject
const p4 = new Promise(() => {
throw new Error('thrown');
});
p4.catch((e) => console.log(e.message)); // 'thrown'
3.4 resolve 与 reject 的不可逆性
const p = new Promise((resolve, reject) => {
resolve('first');
resolve('second'); // 静默忽略
reject(new Error('third')); // 静默忽略
});
p.then((v) => console.log(v)); // 'first'
第一次调用 resolve/reject 后,后续调用都是 no-op。这是 Promise A+ 单次转换公理的直接体现。
3.5 resolve 的 thenable 解析
resolve 不仅可以接收普通值,还可接收 Promise 或 thenable:
// resolve 一个 Promise
const inner = new Promise((resolve) => setTimeout(() => resolve('inner'), 100));
const outer = new Promise((resolve) => resolve(inner));
outer.then((v) => console.log(v)); // 100ms 后输出 'inner'
// resolve 一个 thenable
const thenable = {
then(resolve) {
setTimeout(() => resolve('thenable'), 100);
},
};
const p = new Promise((resolve) => resolve(thenable));
p.then((v) => console.log(v)); // 100ms 后输出 'thenable'
resolve 一个 thenable 时,会触发 PromiseResolveThenableJob,这是一个微任务,确保 thenable 的 then 方法在异步上下文中调用,避免同步递归导致的栈溢出。
4. 状态机详解
4.1 三种状态的语义
| 状态 | 含义 | 可转换到 | 可访问属性 |
|---|---|---|---|
| pending | 等待中,未决定 | fulfilled/rejected | 无 |
| fulfilled | 已完成,有最终值 | 无 | value |
| rejected | 已失败,有拒绝原因 | 无 | reason |
4.2 状态访问器
ES6 Promise 实例提供三个只读属性(通过 getter):
const p = new Promise(() => {});
console.log(p instanceof Promise); // true
// p.state 无法直接访问,规范未暴露
// 仅可通过 then/catch/finally 观察状态
注意:Promise 规范未提供直接读取状态的 API。Node.js 提供了非标准的 p.inspect()(已被废弃)与 util.inspect.custom 用于调试,但生产代码应避免依赖。
4.3 状态观察与回调时机
const p = new Promise((resolve) => setTimeout(() => resolve('done'), 100));
// 注册时 pending
p.then((v) => console.log('fulfilled:', v));
// 100ms 后:p 转为 fulfilled,微任务执行输出 'fulfilled: done'
// 已 settled 的 Promise,then 仍异步触发
Promise.resolve('immediate').then((v) => console.log(v));
console.log('sync');
// 输出:sync, immediate (微任务在同步代码后)
4.4 状态转换的原子性
Promise 的状态转换是原子的:从 pending 到 fulfilled/rejected 的瞬间,所有已注册的回调都被加入微任务队列。多个 then 不会按调用顺序分散触发,而是统一在状态变化后批量调度:
const p = new Promise((resolve) => setTimeout(() => resolve('x'), 100));
p.then((v) => console.log('first:', v));
p.then((v) => console.log('second:', v));
p.then((v) => console.log('third:', v));
// 100ms 后:'first', 'second', 'third' 按注册顺序输出
5. then / catch / finally
5.1 then 的完整签名
promise.then(onFulfilled, onRejected)
- onFulfilled:可选,接收 value 的函数,返回值传递给下一个 Promise
- onRejected:可选,接收 reason 的函数,返回值传递给下一个 Promise
- 返回:新的 Promise
5.2 值穿透
当 onFulfilled 不是函数时,值会被原样传递:
Promise.resolve(42)
.then() // onFulfilled 不是函数,值穿透
.then((v) => console.log(v)); // 42
Promise.reject(new Error('err'))
.then(() => 1) // onFulfilled 被跳过
.catch((e) => console.log(e.message)); // 'err'
5.3 链式调用
then 返回新 Promise,链式调用形成异步管道:
Promise.resolve(1)
.then((n) => n + 1) // 2
.then((n) => n * 2) // 4
.then((n) => Promise.resolve(n + 10)) // 14,Promise 自动解包
.then((n) => console.log(n)); // 14
5.4 错误传播
then 链中任一节点的异常都会被下一个 onRejected 捕获:
Promise.resolve(1)
.then((n) => {
throw new Error('boom');
})
.then((n) => console.log('skipped')) // 被跳过
.catch((e) => console.log('caught:', e.message)); // 'caught: boom'
.then(() => console.log('recovered')); // 链可继续
5.5 catch 的等价形式
p.catch(onRejected) 等价于 p.then(undefined, onRejected):
Promise.reject(new Error('err'))
.catch((e) => console.log(e.message)); // 'err'
// 等价于
Promise.reject(new Error('err'))
.then(undefined, (e) => console.log(e.message));
5.6 finally 的语义
p.finally(onFinally) 在 Promise settled 后(无论成功或失败)执行 onFinally,且不接收参数,不改变链上的值:
Promise.resolve('result')
.finally(() => console.log('cleanup')) // 输出 'cleanup',不接收参数
.then((v) => console.log(v)); // 'result'
Promise.reject(new Error('err'))
.finally(() => console.log('cleanup')) // 输出 'cleanup'
.catch((e) => console.log(e.message)); // 'err'
注意:finally 的回调若抛错或返回 rejected Promise,会”接管”链:
Promise.resolve('result')
.finally(() => {
throw new Error('cleanup failed');
})
.then((v) => console.log(v)) // 被跳过
.catch((e) => console.log(e.message)); // 'cleanup failed'
5.7 then 返回 Promise 的解析规则
then 的回调可返回多种值,Promise 自动解析:
// 返回普通值
Promise.resolve(1)
.then(() => 2) // fulfilled with 2
.then((v) => console.log(v)); // 2
// 返回 Promise
Promise.resolve(1)
.then(() => Promise.resolve(2)) // 等 inner settled
.then((v) => console.log(v)); // 2
// 返回 thenable
Promise.resolve(1)
.then(() => ({ then: (resolve) => resolve(2) }))
.then((v) => console.log(v)); // 2
// 抛错
Promise.resolve(1)
.then(() => { throw new Error('err'); }) // rejected
.catch((e) => console.log(e.message)); // 'err'
// 无返回值
Promise.resolve(1)
.then(() => {}) // fulfilled with undefined
.then((v) => console.log(v)); // undefined
6. thenable 鸭子类型
6.1 thenable 的定义
Promise A+ 规范定义 thenable 为:
“thenable is an object or function that defines a then method.”
即:任何含 then 方法的对象或函数都是 thenable。这是典型的鸭子类型(Duck Typing):不看类型,只看行为。
6.2 PromiseResolveThenableJob
当 resolve 接收 thenable 时,引擎触发 PromiseResolveThenableJob,在微任务中调用 thenable.then:
const thenable = {
then(resolve, reject) {
console.log('thenable.then called');
resolve(42);
},
};
const p = new Promise((resolve) => {
console.log('before resolve');
resolve(thenable);
console.log('after resolve');
});
p.then((v) => console.log('p fulfilled:', v));
console.log('sync end');
// 输出顺序:
// before resolve
// thenable.then called (注意:在 resolve 调用时同步调用 then,而非微任务)
// after resolve
// sync end
// p fulfilled: 42 (微任务)
注意:不同引擎行为略有差异,V8 在 ES2015 早期曾将 thenable.then 调用放入微任务,后续为符合规范改为同步调用。具体细节参见 ECMAScript 规范 PromiseJobs 章节。
6.3 防止多次调用
thenable 的 then 可能被多次调用(有意或无意),Promise 必须确保 resolve/reject 只生效一次:
const evilThenable = {
then(resolve) {
resolve('first');
resolve('second'); // 应被忽略
setTimeout(() => resolve('third'), 100); // 应被忽略
},
};
Promise.resolve(evilThenable).then((v) => console.log(v)); // 'first'
引擎通过 alreadyResolved 记录确保 only-once 语义。
6.4 thenable 与跨框架互操作
thenable 是 Promise 生态的”通用语言”:任何框架(Q.js、when.js、jQuery.Deferred、axios、Bluebird)的 Promise-like 对象,只要有 then 方法,都能被原生 Promise 接收:
// jQuery Deferred(老版本非 Promise A+ 兼容)
const deferred = $.Deferred();
deferred.resolve('jquery');
Promise.resolve(deferred).then((v) => console.log(v)); // 'jquery' (注意老版 jQuery 异常)
// 自定义 thenable
class MyFuture {
#callbacks = [];
#value;
#settled = false;
resolve(value) {
if (this.#settled) return;
this.#settled = true;
this.#value = value;
this.#callbacks.forEach((cb) => cb(value));
}
then(onFulfilled) {
return new MyFuture((resolve) => {
const handle = (v) => {
const result = onFulfilled ? onFulfilled(v) : v;
if (result && typeof result.then === 'function') {
result.then(resolve);
} else {
resolve(result);
}
};
if (this.#settled) {
queueMicrotask(() => handle(this.#value));
} else {
this.#callbacks.push(() => queueMicrotask(() => handle(this.#value)));
}
});
}
}
// 互操作
const future = new MyFuture((resolve) => setTimeout(() => resolve('future'), 100));
Promise.resolve(future).then((v) => console.log(v)); // 100ms 后 'future'
6.5 thenable 陷阱
thenable 鸭子类型带来的问题:
- 误判:任何含 then 属性的对象都被视为 thenable
const obj = { then: 42 }; // then 不是函数,不会触发解析
const obj2 = { then: () => {} }; // then 是函数,会被解析!
Promise.resolve(obj2).then((v) => console.log(v)); // 行为复杂,依赖 obj2.then 实现
- getter 副作用:then 是 getter 时,每次访问都会触发
let count = 0;
const obj = {
get then() {
count++;
return (resolve) => resolve(count);
},
};
Promise.resolve(obj).then((v) => console.log(v));
console.log(count); // 不确定,依赖引擎实现
- 同步访问 then 抛错
const obj = Object.create(null);
Object.defineProperty(obj, 'then', {
get() {
throw new Error('cannot access then');
},
});
Promise.resolve(obj).catch((e) => console.log(e.message)); // 'cannot access then'
7. Promise A+ 规范详解
7.1 规范结构
Promise A+ 规范文档结构:
- Terminology(术语):定义 promise、thenable、value、reason、exception
- Requirements(要求)
- 2.1 Promise States
- 2.2 The then Method
- 2.3 The Promise Resolution Procedure
7.2 状态要求(2.1)
- 2.1.1 Pending:可转为 fulfilled 或 rejected
- 2.1.2 Fulfilled:
- 不可转为其他状态
- 必须有 value,且不可变
- 2.1.3 Rejected:
- 不可转为其他状态
- 必须有 reason,且不可变
7.3 then 方法要求(2.2)
promise.then(onFulfilled, onRejected):
- 2.2.1 onFulfilled 与 onRejected 都是可选参数
- 2.2.1.1 若不是函数,忽略
- 2.2.1.2 onFulfilled 仅在 fulfilled 时调用,参数为 value
- 2.2.1.3 onRejected 仅在 rejected 时调用,参数为 reason
- 2.2.2 onFulfilled 与 onRejected 必须异步执行(作为微任务)
- 2.2.3 onFulfilled/onRejected 最多调用一次
- 2.2.4 then 可在同一 Promise 上多次调用,按注册顺序执行
- 2.2.5 then 必须返回 Promise:
promise2 = promise1.then(onFulfilled, onRejected)- 若 onFulfilled/onRejected 抛错,promise2 reject 该错误
- 若 onFulfilled 返回值 x,运行
[[Resolve]](promise2, x) - 若 onFulfilled/onRejected 不是函数,promise2 “继承” promise1 的状态与值/原因
7.4 解析过程(2.3)
[[Resolve]](promise, x):
- 2.3.1 若 promise 与 x 同一引用:reject TypeError
- 2.3.2 若 x 是 Promise:采用 x 的状态
- 2.3.3 若 x 是对象或函数:
- 2.3.3.1 取
then = x.then(可能抛错) - 2.3.3.2 若取 then 抛错,reject 该错误
- 2.3.3.3 若 then 是函数,调用
then.call(x, resolvePromise, rejectPromise):- resolvePromise(y):递归
[[Resolve]](promise, y) - rejectPromise(r):reject(r)
- 防止多次调用(only-once)
- 若 then 抛错且未调用 resolve/reject,reject 该错误
- resolvePromise(y):递归
- 2.3.3.4 若 then 不是函数,fulfill(x)
- 2.3.3.1 取
- 2.3.4 若 x 不是对象或函数,fulfill(x)
7.5 A+ 测试套件
Promise A+ 官方提供 872 个测试用例,位于 promises-aplus-tests 包:
npm install promises-aplus-tests
npx promises-aplus-tests my-promise-implementation.js
实现需提供 deferred() 工厂方法:
MyPromise.deferred = function () {
let resolve, reject;
const promise = new MyPromise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
module.exports = MyPromise;
7.6 与原生 Promise 的差异
| 方面 | A+ 规范 | ES6 Promise |
|---|---|---|
| 状态访问 | 不暴露 | 不暴露 |
| then 返回值 | 必须返回 Promise | 必须返回 Promise(子类) |
| 异步执行 | 微任务或宏任务 | 微任务(PromiseJob) |
| 子类化 | 未规定 | 支持(SpeciesPattern) |
| 静态方法 | 未规定 | resolve/reject/all/race |
| allSettled/any | 未规定 | ES2020/ES2021 引入 |
| withResolvers | 未规定 | ES2024 引入 |
8. 微任务调度
8.1 事件循环模型
HTML 规范定义的事件循环(Event Loop):
while (true) {
// 1. 取出一个宏任务(task)
task = taskQueue.shift();
task.run();
// 2. 清空微任务队列(microtasks)
while (microtaskQueue.length > 0) {
microtask = microtaskQueue.shift();
microtask.run();
}
// 3. 渲染(UI 更新,仅浏览器)
if (needsRender) render();
// 4. 下一轮
}
8.2 微任务来源
微任务包括:
- Promise.then/catch/finally 的回调
- queueMicrotask(fn) 注册的函数
- MutationObserver 的回调
- IntersectionObserver 的回调
- Object.observe(已废弃)
8.3 then 的微任务调度
then 的回调以 PromiseReactionJob 形式入队:
console.log('1. script start');
setTimeout(() => console.log('2. setTimeout'), 0);
Promise.resolve()
.then(() => console.log('3. promise1'))
.then(() => console.log('4. promise2'));
console.log('5. script end');
// 输出顺序:1, 5, 3, 4, 2
// 解析:同步代码(1, 5)→ 微任务(3, 4)→ 宏任务(2)
8.4 微任务的优先级
不同微任务有不同优先级(实现相关,规范未严格规定):
- Chromium:Promise 微任务与 queueMicrotask 同优先级,先入先出
- Firefox:类似 Chromium
- Safari:Promise 微任务优先级略高于 queueMicrotask(在某些场景)
8.5 微任务饥饿
长时间清空微任务队列会导致宏任务饥饿:
function infiniteMicrotask() {
Promise.resolve().then(infiniteMicrotask);
}
infiniteMicrotask();
// setTimeout 等宏任务永远无法执行
这是为什么不能在 then 中执行长任务的原因,生产代码应将长任务切分为宏任务或用 requestIdleCallback。
8.6 微任务与 async/await
async 函数中,await 后的代码以微任务形式调度,等价于 then:
async function foo() {
console.log('1. before await');
await Promise.resolve();
console.log('2. after await');
}
foo();
console.log('3. sync end');
// 输出:1, 3, 2
V8 早期将 await 包装为两层 Promise,后续优化为一层(参见 V8 blog 2017 文章 Faster async functions and promises)。
9. Promise.withResolvers (ES2024)
9.1 设计动机
在 ES2024 之前,若需在构造器外部使用 resolve/reject,需手动提取:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
// 使用 resolve/reject
resolve('done');
这种模式存在多个问题:
- 样板代码:每次都需声明外部变量
- 作用域污染:resolve/reject 暴露到外部作用域
- 类型推断困难:TypeScript 难以推断 resolve/reject 的类型
- 可读性差:意图不明显
9.2 withResolvers 的签名
const { promise, resolve, reject } = Promise.withResolvers();
返回一个对象,包含:
- promise:新的 Promise 实例
- resolve:将 promise 从 pending 转为 fulfilled 的函数
- reject:将 promise 从 pending 转为 rejected 的函数
9.3 应用场景
9.3.1 事件包装
function waitForEvent(element, eventName) {
const { promise, resolve } = Promise.withResolvers();
element.addEventListener(eventName, resolve, { once: true });
return promise;
}
// 使用
const button = document.querySelector('#myButton');
const clickEvent = await waitForEvent(button, 'click');
console.log('按钮被点击', clickEvent);
9.3.2 流式处理
function createReadableStreamFromAsyncIterator(asyncIterator) {
const { promise, resolve, reject } = Promise.withResolvers();
return new ReadableStream({
async pull(controller) {
try {
const { value, done } = await asyncIterator.next();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
resolve();
} catch (err) {
reject(err);
throw err;
}
},
});
}
9.3.3 超时控制
function withTimeout(promise, ms) {
const { promise: timeoutPromise, resolve } = Promise.withResolvers();
const timer = setTimeout(() => resolve('timeout'), ms);
return Promise.race([
promise.finally(() => clearTimeout(timer)),
timeoutPromise,
]);
}
// 使用
const result = await withTimeout(fetch('/api'), 5000);
if (result === 'timeout') {
console.log('请求超时');
}
9.3.4 一次性信号
class Signal {
#promise;
#resolve;
constructor() {
const { promise, resolve } = Promise.withResolvers();
this.#promise = promise;
this.#resolve = resolve;
}
wait() {
return this.#promise;
}
emit(value) {
this.#resolve(value);
}
}
const ready = new Signal();
// 生产者
ready.emit('data');
// 消费者
const data = await ready.wait();
console.log(data); // 'data'
9.3.5 异步队列
class AsyncQueue {
#queue = [];
#pending = [];
enqueue(value) {
if (this.#pending.length > 0) {
const { resolve } = this.#pending.shift();
resolve(value);
} else {
this.#queue.push(value);
}
}
async dequeue() {
if (this.#queue.length > 0) {
return this.#queue.shift();
}
const { promise, resolve } = Promise.withResolvers();
this.#pending.push({ promise, resolve });
return promise;
}
}
const queue = new AsyncQueue();
queue.enqueue(1);
console.log(await queue.dequeue()); // 1
queue.enqueue(2);
console.log(await queue.dequeue()); // 2
9.4 与传统写法的对比
// 传统写法
function oldDelay(ms) {
let resolve;
const promise = new Promise((res) => {
resolve = res;
});
setTimeout(() => resolve('done'), ms);
return promise;
}
// 新写法
function newDelay(ms) {
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve('done'), ms);
return promise;
}
新写法的优势:
- 声明即解构,一步到位
- resolve/reject 与 promise 同级,逻辑清晰
- TypeScript 类型推断友好
- 减少样板代码,可读性更高
9.5 polyfill
if (typeof Promise.withResolvers !== 'function') {
Promise.withResolvers = function () {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
}
10. 并发原语
10.1 Promise.all
语义:等待所有 Promise 都 fulfilled,任一 rejected 立即 reject。
const results = await Promise.all([
fetch('/api/users'),
fetch('/api/products'),
fetch('/api/orders'),
]);
// results 是三个 Response 组成的数组,顺序与输入一致
特点:
- 输入:可迭代对象(数组、Set、Generator 等)
- 输出:Promise<Array
> - 失败模式:fast-fail,任一 reject 立即 reject
- 顺序保证:结果数组顺序与输入一致(无论完成顺序)
手动实现:
Promise.all = function (iterable) {
return new Promise((resolve, reject) => {
const arr = Array.from(iterable);
const results = new Array(arr.length);
let remaining = arr.length;
if (remaining === 0) {
return resolve([]);
}
arr.forEach((p, i) => {
Promise.resolve(p).then(
(v) => {
results[i] = v;
if (--remaining === 0) resolve(results);
},
reject
);
});
});
};
10.2 Promise.race
语义:返回第一个 settled 的 Promise(无论 fulfilled 或 rejected)。
const result = await Promise.race([
fetch('/api/primary'),
fetch('/api/backup'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), 5000)
),
]);
特点:
- 输入:可迭代对象
- 输出:第一个 settled 的 Promise 的状态与值/原因
- 失败模式:第一个 reject 即 reject(也可能第一个 fulfilled 即 fulfilled,取决于谁先 settled)
- 取消语义:race 本身不取消其他 Promise,它们仍会执行,只是结果被忽略
手动实现:
Promise.race = function (iterable) {
return new Promise((resolve, reject) => {
Array.from(iterable).forEach((p) => {
Promise.resolve(p).then(resolve, reject);
});
});
};
10.3 Promise.allSettled (ES2020)
语义:等待所有 Promise 都 settled(无论 fulfilled 或 rejected),返回每个的状态与值/原因。
const results = await Promise.allSettled([
fetch('/api/ok'),
fetch('/api/fail'),
]);
// [
// { status: 'fulfilled', value: Response },
// { status: 'rejected', reason: Error }
// ]
特点:
- 输入:可迭代对象
- 输出:Promise<Array<{ status, value? | reason? }>>
- 失败模式:不 fail-fast,等待所有完成
- 适用场景:批量请求中希望知道每个的结果,而非整体失败
手动实现:
Promise.allSettled = function (iterable) {
return new Promise((resolve) => {
const arr = Array.from(iterable);
const results = new Array(arr.length);
let remaining = arr.length;
if (remaining === 0) return resolve([]);
arr.forEach((p, i) => {
Promise.resolve(p).then(
(v) => {
results[i] = { status: 'fulfilled', value: v };
if (--remaining === 0) resolve(results);
},
(r) => {
results[i] = { status: 'rejected', reason: r };
if (--remaining === 0) resolve(results);
}
);
});
});
};
10.4 Promise.any (ES2021)
语义:返回第一个 fulfilled 的 Promise,所有都 rejected 才 reject AggregateError。
const result = await Promise.any([
fetch('/api/cdn1'),
fetch('/api/cdn2'),
fetch('/api/cdn3'),
]);
// 任一成功即返回,即使其他失败
特点:
- 输入:可迭代对象
- 输出:第一个 fulfilled 的值
- 失败模式:所有 rejected 才 reject(AggregateError)
- 适用场景:多源备份、竞速获取最快响应
手动实现:
Promise.any = function (iterable) {
return new Promise((resolve, reject) => {
const arr = Array.from(iterable);
const errors = new Array(arr.length);
let remaining = arr.length;
if (remaining === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
arr.forEach((p, i) => {
Promise.resolve(p).then(
resolve,
(r) => {
errors[i] = r;
if (--remaining === 0) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
};
10.5 四种原语对比
| 方法 | 成功条件 | 失败条件 | 输出 | 失败模式 |
|---|---|---|---|---|
| all | 全部 fulfilled | 任一 rejected | Array | fast-fail |
| race | 第一个 settled 为 fulfilled | 第一个 settled 为 rejected | 第一个的值/原因 | fast-fail |
| allSettled | 无(必 fulfilled) | 无(必 fulfilled) | Array<{ status, value/reason }> | 不 fail |
| any | 任一 fulfilled | 全部 rejected | 第一个 fulfilled 的值 | all-reject |
10.6 并发控制
Promise.all 在大量并发时可能压垮后端,需要并发限制:
async function mapWithConcurrency(items, fn, concurrency = 5) {
const results = new Array(items.length);
let index = 0;
async function worker() {
while (index < items.length) {
const i = index++;
try {
results[i] = await fn(items[i], i);
} catch (err) {
results[i] = { error: err };
}
}
}
const workers = Array.from({ length: concurrency }, worker);
await Promise.all(workers);
return results;
}
// 使用:并发 5 个请求
const urls = Array.from({ length: 100 }, (_, i) => `/api/item/${i}`);
const results = await mapWithConcurrency(urls, (url) => fetch(url).then((r) => r.json()), 5);
第三方库 p-limit 提供类似功能:
import pLimit from 'p-limit';
const limit = pLimit(5);
const results = await Promise.all(
urls.map((url) => limit(() => fetch(url).then((r) => r.json())))
);
10.7 高级模式:可取消的并发
结合 AbortController 实现可取消的并发请求:
async function fetchAllWithConcurrency(urls, { concurrency = 5, signal } = {}) {
const controller = new AbortController();
if (signal) {
if (signal.aborted) controller.abort();
else signal.addEventListener('abort', () => controller.abort(), { once: true });
}
const results = new Array(urls.length);
let index = 0;
async function worker() {
while (index < urls.length) {
if (controller.signal.aborted) return;
const i = index++;
try {
const response = await fetch(urls[i], { signal: controller.signal });
results[i] = await response.json();
} catch (err) {
if (err.name === 'AbortError') return;
results[i] = { error: err };
}
}
}
await Promise.all(Array.from({ length: concurrency }, worker));
return results;
}
// 使用
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // 5 秒后取消所有
const data = await fetchAllWithConcurrency(urls, { concurrency: 5, signal: controller.signal });
11. 错误处理链
11.1 错误的两种来源
Promise 链中的错误来自两类:
- 同步抛出:onFulfilled/onRejected 中 throw
- 异步 reject:返回 rejected Promise
// 同步抛出
Promise.resolve()
.then(() => { throw new Error('sync'); })
.catch((e) => console.log(e.message)); // 'sync'
// 异步 reject
Promise.resolve()
.then(() => Promise.reject(new Error('async')))
.catch((e) => console.log(e.message)); // 'async'
两者等价:都会被下一个 onRejected 捕获。
11.2 错误冒泡
未处理的错误会沿链冒泡,直到遇到 onRejected:
Promise.reject(new Error('err'))
.then((v) => console.log('skip 1')) // 跳过
.then((v) => console.log('skip 2')) // 跳过
.then((v) => console.log('skip 3')) // 跳过
.catch((e) => console.log('caught:', e.message)); // 'caught: err'
11.3 catch 的位置
// 反模式:每层都 catch,错误被吞
Promise.resolve(1)
.then((n) => { throw new Error('boom'); })
.catch((e) => console.log('caught at 1')) // 错误在这里被吞
.then((n) => console.log('continue')) // 继续执行,但 n 是 undefined
.catch((e) => console.log('never reached'));
// 正确:只在最外层 catch
Promise.resolve(1)
.then((n) => { throw new Error('boom'); })
.then((n) => console.log('skipped'))
.catch((e) => console.log('caught:', e.message));
11.4 未处理的 rejection
未被任何 catch 捕获的 rejection 会触发 unhandledrejection 事件:
// 浏览器
window.addEventListener('unhandledrejection', (event) => {
console.log('Unhandled rejection:', event.reason);
event.preventDefault(); // 阻止默认行为(控制台输出)
});
// Node.js
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
});
Promise.reject(new Error('lost'));
// 触发 unhandledrejection 事件
11.5 rejectionhandled 事件
若一个之前未处理的 rejection 后来被 catch,会触发 rejectionhandled:
window.addEventListener('unhandledrejection', (e) => {
console.log('unhandled:', e.reason);
});
window.addEventListener('rejectionhandled', (e) => {
console.log('later handled:', e.reason);
});
const p = Promise.reject(new Error('delayed'));
setTimeout(() => {
p.catch((e) => console.log('caught:', e.message));
}, 1000);
// 输出顺序:
// unhandled: Error: delayed
// (1 秒后)
// caught: delayed
// later handled: Error: delayed
11.6 错误处理的最佳实践
- 链尾必 catch:每个 Promise 链末尾必须有 catch 或 finally
- 不要吞错:catch 中至少记录日志
- 区分错误类型:用 instanceof 或错误码
- rethrow 不期望的错误:只处理自己关心的
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (response.status === 404) {
return null; // 用户不存在,返回 null
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (err) {
if (err instanceof TypeError) {
console.error('Network error:', err.message);
throw new Error('Network unavailable');
}
throw err; // 重新抛出未识别的错误
}
}
12. 常见陷阱
12.1 忘记 return
// 反模式
async function getUsers() {
const users = [1, 2, 3].map(async (id) => {
const user = await fetchUser(id); // 缺少 return
});
return Promise.all(users); // users 是 [undefined, undefined, undefined]
}
// 正确
async function getUsers() {
const users = [1, 2, 3].map(async (id) => {
return await fetchUser(id);
});
return Promise.all(users);
}
// 更简洁
async function getUsers() {
return Promise.all([1, 2, 3].map(fetchUser));
}
12.2 在 forEach 中 await
// 反模式:forEach 不等待 Promise
[1, 2, 3].forEach(async (id) => {
await fetchUser(id);
});
console.log('done'); // 在所有 fetchUser 完成前就输出
// 正确:用 for...of 或 Promise.all
for (const id of [1, 2, 3]) {
await fetchUser(id); // 串行
}
await Promise.all([1, 2, 3].map(fetchUser)); // 并行
12.3 创建不使用的 Promise
// 反模式:executor 内的异步操作无法取消
const p = new Promise((resolve) => {
setTimeout(() => resolve('done'), 1000);
});
p.then((v) => console.log(v));
// 即使取消 p,setTimeout 仍会执行
// 改进:用 Promise.withResolvers + 清理逻辑
function delay(ms) {
const { promise, resolve } = Promise.withResolvers();
const timer = setTimeout(() => resolve('done'), ms);
promise.finally(() => clearTimeout(timer));
return promise;
}
12.4 同步 resolve 与异步执行的混淆
// 反模式:期望 then 立即执行
const p = Promise.resolve('x');
console.log('before');
p.then((v) => console.log(v));
console.log('after');
// 输出:before, after, x
// 而非:before, x, after
// 解析:即使 Promise 已 settled,then 回调仍异步执行(微任务)
12.5 多次 then 同一 Promise
const p = Promise.resolve('x');
p.then((v) => console.log('first:', v)); // 'first: x'
p.then((v) => console.log('second:', v)); // 'second: x'
// 两个 then 都会执行,不是"二选一"
// 若想"二选一",应用 race 或分支
12.6 Promise 与 try/catch 的混淆
// 反模式:try/catch 无法捕获 Promise 内的异常
try {
Promise.resolve().then(() => { throw new Error('async'); });
} catch (err) {
console.log('caught:', err.message); // 不会执行
}
// 正确:用 catch 或 await
Promise.resolve()
.then(() => { throw new Error('async'); })
.catch((err) => console.log('caught:', err.message)); // 'caught: async'
// 或
async function foo() {
try {
await Promise.resolve().then(() => { throw new Error('async'); });
} catch (err) {
console.log('caught:', err.message); // 'caught: async'
}
}
12.7 在 Promise 链中混用同步与异步
// 反模式:在 then 中调用同步函数,错误无法捕获
const fs = require('fs');
Promise.resolve()
.then(() => {
// 同步抛出的错误能被捕获
JSON.parse('invalid'); // 抛 SyntaxError
})
.catch((e) => console.log('caught:', e.message));
// 但在事件回调中抛错无法被 Promise 捕获
Promise.resolve()
.then(() => {
setTimeout(() => { throw new Error('async in callback'); }, 100);
})
.catch((e) => console.log('caught:', e.message)); // 不会捕获
12.8 Promise.all 的 fast-fail 陷阱
// 反模式:期望 Promise.all 即使部分失败也能得到结果
const results = await Promise.all([
fetch('/api/ok'),
fetch('/api/fail'), // 假设返回 500
]);
// results 永远不会到达,因为 fetch 不会因 500 reject
// 改进 1:用 allSettled
const results = await Promise.allSettled([
fetch('/api/ok'),
fetch('/api/fail'),
]);
// results = [{ status: 'fulfilled', value: Response }, { status: 'fulfilled', value: Response }]
// 改进 2:在 map 中包装错误
const results = await Promise.all(
[fetch('/api/ok'), fetch('/api/fail')].map((p) =>
p.then(
(r) => ({ ok: true, value: r }),
(e) => ({ ok: false, error: e })
)
)
);
12.9 嵌套 Promise
// 反模式:不必要的嵌套
Promise.resolve(1)
.then((n) => {
return new Promise((resolve) => {
resolve(n + 1); // 多余的嵌套
});
})
.then((n) => console.log(n));
// 正确:用 Promise.resolve 或直接返回
Promise.resolve(1)
.then((n) => Promise.resolve(n + 1))
.then((n) => console.log(n));
Promise.resolve(1)
.then((n) => n + 1)
.then((n) => console.log(n));
12.10 async 函数返回 Promise 的混淆
// 反模式:在 async 函数中返回 Promise.resolve
async function foo() {
return Promise.resolve(42); // 多余,async 已自动包装
}
// 等价于
async function foo() {
return 42;
}
// 反模式:在 async 函数中 await 一个不必要的 Promise
async function bar() {
return await Promise.resolve(42); // 多余的 await
}
// 简化为
async function bar() {
return 42;
}
13. 最佳实践
13.1 优先使用 async/await
// 反模式:Promise 链过长
function fetchData() {
return fetch('/api/data')
.then((r) => r.json())
.then((data) => process(data))
.then((processed) => save(processed))
.then((saved) => notify(saved))
.catch((err) => handleError(err));
}
// 推荐:async/await
async function fetchData() {
try {
const r = await fetch('/api/data');
const data = await r.json();
const processed = process(data);
const saved = await save(processed);
return notify(saved);
} catch (err) {
handleError(err);
}
}
13.2 并行优于串行
// 反模式:串行 await
async function slow() {
const a = await fetch('/api/a');
const b = await fetch('/api/b');
const c = await fetch('/api/c');
return [a, b, c];
}
// 推荐:并行
async function fast() {
return Promise.all([
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c'),
]);
}
13.3 用 allSettled 处理部分失败
async function fetchAll(urls) {
const results = await Promise.allSettled(
urls.map((url) => fetch(url).then((r) => r.json()))
);
const succeeded = results
.filter((r) => r.status === 'fulfilled')
.map((r) => r.value);
const failed = results
.filter((r) => r.status === 'rejected')
.map((r) => r.reason);
if (failed.length > 0) {
console.warn(`${failed.length} requests failed`);
}
return succeeded;
}
13.4 用 any 实现竞速
// 多源数据,任一成功即返回
async function fetchWithRedundancy(urls) {
try {
return await Promise.any(urls.map((url) => fetch(url)));
} catch (err) {
throw new Error('All sources failed');
}
}
13.5 用 withResolvers 简化外部 resolve
// 反模式
function oldWait() {
let resolve;
const p = new Promise((res) => { resolve = res; });
setTimeout(() => resolve('done'), 1000);
return p;
}
// 推荐
function newWait() {
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve('done'), 1000);
return promise;
}
13.6 始终处理 rejection
// 反模式:不处理 rejection
function bad() {
return fetch('/api/data'); // 若 reject,unhandledrejection
}
// 推荐:返回前包装或确保调用方 catch
function good() {
return fetch('/api/data').catch((err) => {
console.error('Fetch failed:', err);
throw err; // 重新抛出让调用方决定
});
}
13.7 用 Promise 缓存
const cache = new Map();
function cachedFetch(url) {
if (cache.has(url)) {
return cache.get(url); // 返回缓存的 Promise
}
const promise = fetch(url)
.then((r) => r.json())
.catch((err) => {
cache.delete(url); // 失败时清缓存,允许重试
throw err;
});
cache.set(url, promise);
return promise;
}
注意:缓存的 Promise 一旦 rejected,后续调用都会得到同样的 rejection,需在 catch 中清理。
13.8 用队列控制时序
class PromiseQueue {
#queue = [];
#running = 0;
#concurrency;
constructor(concurrency = 1) {
this.#concurrency = concurrency;
}
add(task) {
return new Promise((resolve, reject) => {
this.#queue.push({ task, resolve, reject });
this.#run();
});
}
#run() {
while (this.#running < this.#concurrency && this.#queue.length > 0) {
const { task, resolve, reject } = this.#queue.shift();
this.#running++;
Promise.resolve()
.then(task)
.then(resolve, reject)
.finally(() => {
this.#running--;
this.#run();
});
}
}
}
const queue = new PromiseQueue(2); // 并发 2
queue.add(() => fetch('/api/1'));
queue.add(() => fetch('/api/2'));
queue.add(() => fetch('/api/3')); // 等前两个之一完成
14. 性能优化
14.1 微任务开销
每次 then 调用都会创建微任务,微任务过多会延迟宏任务:
// 反模式:大量 then 链
function sum(n) {
let p = Promise.resolve(0);
for (let i = 1; i <= n; i++) {
p = p.then((acc) => acc + i);
}
return p;
}
// sum(100000) 创建 100000 个微任务,可能阻塞数秒
// 推荐:同步累加
function sumSync(n) {
let acc = 0;
for (let i = 1; i <= n; i++) acc += i;
return Promise.resolve(acc);
}
14.2 Promise 创建开销
new Promise() 创建对象、分配内存、维护回调数组,大量创建时有开销:
// 反模式:在热路径中创建 Promise
function frequent() {
return new Promise((resolve) => resolve(42)); // 每次创建新 Promise
}
// 推荐:复用已 settled 的 Promise
const cached = Promise.resolve(42);
function frequent() {
return cached;
}
14.3 async/await 的优化
V8 引擎对 async/await 做了多次优化:
- ES2017 原始:每个 await 创建两层 Promise
- ES2018+:减少为层 Promise(参见 V8 blog Faster async functions and promises)
- V8 7.2+:await 即使是非 Promise 值也只跳过一帧微任务
// 现代 V8 中,以下两个函数性能接近
async function foo1() {
return await Promise.resolve(42);
}
function foo2() {
return Promise.resolve(42);
}
14.4 Promise.all 的并行度
Promise.all 不会限制并发,所有 Promise 同时启动:
// 反模式:同时发起 10000 个请求
const results = await Promise.all(
Array.from({ length: 10000 }, (_, i) => fetch(`/api/${i}`))
);
// 可能导致浏览器连接数耗尽、后端压力过大
// 推荐:分批或并发限制
async function batchAll(items, fn, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
results.push(...await Promise.all(batch.map(fn)));
}
return results;
}
14.5 内存泄漏
长生命周期对象持有 Promise 可能造成泄漏:
// 反模式:全局 Promise 持有大对象
let bigPromise;
function load() {
bigPromise = new Promise((resolve) => {
const huge = new Array(1e6).fill(0); // 1M 元素
setTimeout(() => resolve(huge), 1000);
});
}
// bigPromise 引用 huge,即使不再使用也不会被 GC
// 推荐:用完即清
function load() {
const promise = new Promise((resolve) => {
const huge = new Array(1e6).fill(0);
setTimeout(() => resolve(huge), 1000);
});
return promise.then((data) => {
// 处理后释放
const result = data.length;
return result;
});
}
15. 测试
15.1 单元测试基础
import { describe, it, expect } from 'vitest';
describe('fetchUser', () => {
it('should return user when fetch succeeds', async () => {
const mockFetch = (url) =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ id: 1, name: 'Alice' }),
});
global.fetch = mockFetch;
const user = await fetchUser(1);
expect(user).toEqual({ id: 1, name: 'Alice' });
});
it('should return null when 404', async () => {
global.fetch = () =>
Promise.resolve({ ok: true, status: 404 });
const user = await fetchUser(1);
expect(user).toBeNull();
});
it('should throw on network error', async () => {
global.fetch = () => Promise.reject(new TypeError('Network error'));
await expect(fetchUser(1)).rejects.toThrow('Network unavailable');
});
});
15.2 Promise A+ 测试套件
实现 A+ 兼容的 Promise 后,用 promises-aplus-tests 验证:
// my-promise.js
class MyPromise {
// ... 实现 ...
static deferred() {
let resolve, reject;
const promise = new MyPromise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
}
module.exports = MyPromise;
npm install --save-dev promises-aplus-tests
npx promises-aplus-tests my-promise.js
# 应通过 872 个测试
15.3 异步测试技巧
import { describe, it, expect, vi } from 'vitest';
describe('async patterns', () => {
it('should handle Promise.all order', async () => {
const slow = new Promise((r) => setTimeout(() => r('slow'), 100));
const fast = new Promise((r) => setTimeout(() => r('fast'), 10));
const results = await Promise.all([slow, fast]);
expect(results).toEqual(['slow', 'fast']); // 顺序与输入一致
});
it('should handle Promise.race', async () => {
const slow = new Promise((r) => setTimeout(() => r('slow'), 100));
const fast = new Promise((r) => setTimeout(() => r('fast'), 10));
const result = await Promise.race([slow, fast]);
expect(result).toBe('fast');
});
it('should handle unhandledrejection', async () => {
const handler = vi.fn();
process.on('unhandledRejection', handler);
Promise.reject(new Error('test'));
await new Promise((r) => setImmediate(r));
expect(handler).toHaveBeenCalled();
process.off('unhandledRejection', handler);
});
});
15.4 Mock Promise 时间
import { vi } from 'vitest';
it('should resolve after delay', async () => {
vi.useFakeTimers();
const promise = new Promise((r) => setTimeout(() => r('done'), 1000));
vi.advanceTimersByTime(500);
// 此时 promise 仍 pending
vi.advanceTimersByTime(500);
expect(await promise).toBe('done');
vi.useRealTimers();
});
16. 与其他异步模型的对比
16.1 Promise vs 回调
| 方面 | 回调 | Promise |
|---|---|---|
| 组合性 | 低(嵌套) | 高(链式) |
| 错误处理 | 手动传递 err | 自动冒泡 |
| 多次触发 | 支持 | 不支持(单次) |
| 并发 | 难(counter 模式) | 易(all/race) |
| 取消 | 通过 token | 通过 AbortSignal |
| 可读性 | 差(回调地狱) | 好(链) |
16.2 Promise vs async/await
| 方面 | Promise | async/await |
|---|---|---|
| 语法 | then 链 | 同步式 |
| 错误处理 | catch | try/catch |
| 调试 | 栈帧复杂 | 栈帧清晰 |
| 串行 | 显式 return | 直接 await |
| 并行 | Promise.all | await Promise.all |
| 循环 | 需递归或 reduce | for…of + await |
| 本质 | 对象 | 语法糖 |
16.3 Promise vs Observable
| 方面 | Promise | Observable |
|---|---|---|
| 值数量 | 0 或 1 | 0 到多个 |
| 取消 | 不内置 | unsubscribe |
| 惰性 | 立即执行 | 订阅后才执行 |
| 重放 | 不可重放 | 可重放(Subject) |
| 组合 | all/race/allSettled | switchMap/mergeMap |
| 操作符 | 无 | 大量(map/filter/…) |
| 生态 | 原生 | RxJS 等库 |
16.4 Promise vs CSP
CSP(Communicating Sequential Processes)是另一种并发模型,典型实现如 Go 的 channel、JS 的 js-csp:
// js-csp 风格
import { go, chan, put, take } from 'js-csp';
const ch = chan();
go(function* () {
yield put(ch, 1);
yield put(ch, 2);
});
go(function* () {
console.log(yield take(ch)); // 1
console.log(yield take(ch)); // 2
});
Promise 适合”一次性结果”的场景,CSP 适合”持续通信”的场景。
16.5 Promise vs Generator
Generator 可作为 Promise 的”手动实现”:
// 用 Generator 模拟 async/await
function* gen() {
const a = yield fetch('/api/a');
const b = yield fetch('/api/b');
return [a, b];
}
function run(generator) {
const it = generator();
function step(arg) {
const { value, done } = it.next(arg);
if (done) return Promise.resolve(value);
return Promise.resolve(value).then(step);
}
return step();
}
run(gen).then(([a, b]) => console.log(a, b));
这是 co、koa 等库的核心原理,async/await 即是这种模式的语法糖。
17. 案例研究:并发请求管理器
17.1 需求
设计一个生产级并发请求管理器,要求:
- 并发限制(默认 5)
- 优先级队列
- 超时控制
- 自动重试(指数退避)
- 取消支持
- 进度回调
- 错误统计
17.2 实现
class RequestManager {
#queue = [];
#running = 0;
#concurrency;
#stats = { total: 0, succeeded: 0, failed: 0 };
constructor({ concurrency = 5 } = {}) {
this.#concurrency = concurrency;
}
request(url, options = {}, {
priority = 0,
timeout = 30000,
retries = 3,
signal,
onProgress,
} = {}) {
return new Promise((resolve, reject) => {
const task = {
url,
options,
priority,
timeout,
retries,
signal,
onProgress,
resolve,
reject,
attempt: 0,
};
this.#enqueue(task);
});
}
#enqueue(task) {
// 按优先级插入
const i = this.#queue.findIndex((t) => t.priority < task.priority);
if (i === -1) {
this.#queue.push(task);
} else {
this.#queue.splice(i, 1, task);
}
this.#run();
}
#run() {
while (this.#running < this.#concurrency && this.#queue.length > 0) {
const task = this.#queue.shift();
this.#running++;
this.#execute(task).finally(() => {
this.#running--;
this.#run();
});
}
}
async #execute(task) {
const { url, options, timeout, retries, signal, onProgress, resolve, reject } = task;
while (true) {
if (signal?.aborted) {
return reject(new DOMException('Aborted', 'AbortError'));
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
if (signal) {
signal.addEventListener('abort', () => controller.abort(), { once: true });
}
try {
this.#stats.total++;
const response = await fetch(url, {
...options,
signal: controller.signal,
});
if (response.body && onProgress) {
const reader = response.body.getReader();
let received = 0;
const contentLength = +response.headers.get('Content-Length') || 0;
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
onProgress({ received, total: contentLength });
}
const blob = new Blob(chunks);
resolve(new Response(blob, { headers: response.headers, status: response.status }));
} else {
resolve(response);
}
this.#stats.succeeded++;
return;
} catch (err) {
if (err.name === 'AbortError' && task.attempt < retries) {
task.attempt++;
const delay = Math.pow(2, task.attempt) * 1000;
await new Promise((r) => setTimeout(r, delay));
continue; // 重试
}
this.#stats.failed++;
reject(err);
return;
} finally {
clearTimeout(timer);
}
}
}
getStats() {
return { ...this.#stats, queueLength: this.#queue.length, running: this.#running };
}
}
17.3 使用
const manager = new RequestManager({ concurrency: 5 });
// 普通请求
const response = await manager.request('/api/users');
// 高优先级 + 进度
const largeFile = await manager.request(
'/api/large-file',
{},
{
priority: 10,
timeout: 60000,
onProgress: ({ received, total }) => {
console.log(`Progress: ${received}/${total}`);
},
}
);
// 可取消
const controller = new AbortController();
manager.request('/api/slow', {}, { signal: controller.signal }).catch((err) => {
console.log('Cancelled:', err.name);
});
controller.abort();
// 统计
console.log(manager.getStats());
// { total: 3, succeeded: 2, failed: 1, queueLength: 0, running: 0 }
17.4 设计权衡
- 优先级队列:用线性查找插入,简单但 O(n);生产可用二叉堆 O(log n)
- 重试策略:指数退避 + 抖动(简化版未加抖动);生产建议加 jitter
- 取消语义:AbortController 嵌套,外层取消触发内层;但已发出的请求无法真正取消(只能忽略结果)
- 进度回调:依赖 ReadableStream;不支持流式响应的浏览器无法获取进度
- 统计:简单计数器;生产可加 Prometheus 埋点
- 错误分类:未区分网络错误、超时、HTTP 错误;生产应细分
18. 习题
18.1 基础题
题 1(填空):Promise 构造器的执行器函数接收两个参数,分别命名为 ______ 和 ______,前者将状态从 pending 转为 ______,后者将状态从 pending 转为 ______。
题 2(选择):下列哪种情况会导致 Promise 永远 pending?
A. const p = new Promise((resolve) => resolve(42));
B. const p = new Promise(() => {});
C. const p = new Promise((_, reject) => reject(new Error('x')));
D. const p = Promise.resolve(42);
题 3(选择):Promise.all 的失败模式是什么?
A. 全部 reject 才 reject
B. 任一 reject 立即 reject
C. 等待所有完成才 reject
D. 不 reject,返回部分结果
18.2 中级题
题 4(代码修复):以下代码意图实现”按顺序获取用户的所有订单”,但实际并行执行。请修复。
async function getOrdersForUsers(userIds) {
return userIds.map(async (id) => {
const user = await fetchUser(id);
return fetchOrders(user.id);
});
}
题 5(代码编写):实现 Promise.delay(ms, value),返回 ms 毫秒后 resolve 的 Promise,值为 value。
题 6(分析):分析以下代码输出顺序。
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
queueMicrotask(() => console.log(4));
console.log(5);
18.3 高级题
题 7(开放设计):设计一个”可取消的 Promise.race”实现,要求:
- 接收多个 Promise 与一个 AbortSignal
- signal aborted 时,立即 reject AbortError
- 第一个 Promise settled 时,signal 监听器应被清理
题 8(开放设计):实现一个”Promise 缓存”装饰器,要求:
- 同 key 的并发请求共享同一个 Promise
- 失败的 Promise 不缓存
- 支持手动清除缓存
- 支持设置 TTL
请写出完整实现并讨论权衡。
18.4 参考答案
题 1 答案:resolve; reject; fulfilled; rejected
题 2 答案:B。executor 内未调用 resolve 或 reject,Promise 永远 pending。
题 3 答案:B。Promise.all 是 fast-fail,任一 reject 立即 reject。
题 4 答案:
// 问题:map 返回的是 Promise 数组,但 await 在 map 内部不阻塞下一个迭代
// 修复 1:用 for...of 串行
async function getOrdersForUsers(userIds) {
const results = [];
for (const id of userIds) {
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
results.push(orders);
}
return results;
}
// 修复 2:用 reduce 链
async function getOrdersForUsers(userIds) {
return userIds.reduce(async (acc, id) => {
const prev = await acc;
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
return [...prev, orders];
}, Promise.resolve([]));
}
题 5 答案:
Promise.delay = function (ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
};
// 使用
Promise.delay(1000, 'hello').then((v) => console.log(v)); // 1 秒后 'hello'
题 6 答案:输出顺序为 1, 5, 3, 4, 2。
解析:
- 1, 5:同步代码
- 3, 4:微任务(then 与 queueMicrotask 同优先级,按注册顺序)
- 2:宏任务(setTimeout)
题 7 答案:
function cancellableRace(promises, signal) {
return new Promise((resolve, reject) => {
if (signal?.aborted) {
return reject(new DOMException('Aborted', 'AbortError'));
}
const onAbort = () => reject(new DOMException('Aborted', 'AbortError'));
signal?.addEventListener('abort', onAbort, { once: true });
promises.forEach((p) => {
Promise.resolve(p).then(
(v) => {
signal?.removeEventListener('abort', onAbort);
resolve(v);
},
(r) => {
signal?.removeEventListener('abort', onAbort);
reject(r);
}
);
});
});
}
权衡:
- signal 监听器必须在 settled 时移除,避免内存泄漏
- 已发出的 Promise 无法真正取消,只能忽略其结果
- 若 promises 为空且 signal 未 abort,Promise 永远 pending(与 Promise.race 行为一致)
题 8 答案:
function cached(fn, { ttl } = {}) {
const cache = new Map();
function wrapped(key, ...args) {
if (cache.has(key)) {
const entry = cache.get(key);
if (!ttl || Date.now() - entry.time < ttl) {
return entry.promise;
}
cache.delete(key);
}
const promise = Promise.resolve()
.then(() => fn(key, ...args))
.catch((err) => {
cache.delete(key); // 失败不缓存
throw err;
});
cache.set(key, { promise, time: Date.now() });
return promise;
}
wrapped.clear = () => cache.clear();
wrapped.delete = (key) => cache.delete(key);
return wrapped;
}
// 使用
const cachedFetch = cached(async (url) => {
const r = await fetch(url);
return r.json();
}, { ttl: 60000 });
// 同 URL 的并发请求共享同一个 Promise
cachedFetch('/api/users'); // 触发 fetch
cachedFetch('/api/users'); // 复用上面的 Promise
cachedFetch.clear(); // 清空缓存
权衡:
- 同 key 并发共享 Promise:节省请求,但若 Promise rejected,所有等待者都失败
- TTL 简单实现:用 Date.now(),生产可用 setTimeout 主动清理
- 内存:cache 无大小限制,生产可加 LRU
- 失败清理:catch 中 delete,保证下次请求重试
19. 参考文献
-
Promise/A+ Working Group. Promises/A+ Specification. 2014. https://promisesaplus.com/
-
ECMA International. ECMAScript 2025 Language Specification (ECMA-262, 16th Edition) - Promise Objects. 2025. https://tc39.es/ecma262/#sec-promise-objects
-
Mark S. Miller, Tom Van Cutsem, Bill Frantz. Robust Composition: Towards a Principled Approach to the Promise Abstraction. ECOOP 2013. DOI: 10.1007/978-3-642-39038-8_8
-
Claus Brabrand, Andrzej Wasowski. Programming the Web with High-Level Languages. SPLASH 2016. https://www.brics.dk/~wasowski/papers/brabrand2016spl.pdf
-
Domenic Denicola. You’re Missing the Point of Promises. GitHub Gist, 2014. https://gist.github.com/domenic/3889970
-
Kris Kowal. CommonJS Promises/A. CommonJS Wiki, 2009. https://wiki.commonjs.org/wiki/Promises/A
-
Dr. Axel Rauschmayer. Exploring ES6: Promises for asynchronous programming. Leanpub, 2014. https://exploringjs.com/es6/ch_promises.html
-
Kyle Simpson. You Don’t Know JS: Async & Performance. O’Reilly Media, 2016. https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/async%20%26%20performance/README.md
-
Brian Goetz. Java Concurrency in Practice. Addison-Wesley, 2006. https://jcip.net/
-
Barbara Liskov, Liuba Shrira. Promises: Linguistic Support for Efficient Asynchronous Procedure Calls in Distributed Systems. PARCOM 1988. https://dl.acm.org/doi/10.1145/62312.62327
-
Daniel P. Friedman, David S. Wise. The Impact of Applicative Programming on Multiprocessing. ICSE 1976. https://dl.acm.org/doi/10.5555/800253.807632
-
WHATWG. HTML Living Standard - Event Loops. 2025. https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
20. 延伸阅读
20.1 规范与标准
- ECMAScript Promise Objects: https://tc39.es/ecma262/#sec-promise-objects
- Promise A+ Specification: https://promisesaplus.com/
- HTML Event Loops: https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
- Node.js Event Loop: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick
20.2 经典论文
- Promises: Linguistic Support for Efficient Asynchronous Procedure Calls (Liskov & Shrira, 1988)
- Robust Composition (Miller et al., 2013)
- Faster async functions and promises (V8 Blog, 2017)
20.3 教程与博客
- JavaScript Promises: An Introduction (Web Fundamentals): https://web.dev/articles/promises
- We have a problem with promises (Nolan Lawson): https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
- Promise anti-patterns (Tauren Mills): https://taoofcode.net/promise-anti-patterns/
20.4 开源实现
- V8 Promise 实现: https://v8.dev/blog/fast-async
- Bluebird(高性能 Promise 库): https://github.com/petkaantonov/bluebird
- Q.js(Promise A+ 早期实现): https://github.com/kriskowal/q
- when.js: https://github.com/cujojs/when
20.5 相关主题
- async/await(ES2017)
- AbortController(取消原语)
- Web Streams API
- ReactiveX / RxJS(Observable)
- CSP(Communicating Sequential Processes)
- Actor 模型
20.6 进阶主题
- Promise 子类化(SpeciesPattern)
- Promise debugging(Node.js —inspect, Chrome DevTools)
- Promise 性能分析(V8 Profiler)
- Promise 在 Web Worker 中的传递(postMessage 序列化)
- Promise 与 Service Worker 的交互
附录 A:Promise API 完整参考
A.1 静态方法
| 方法 | 引入版本 | 用途 |
|---|---|---|
| Promise.resolve(value) | ES2015 | 返回 fulfilled 的 Promise |
| Promise.reject(reason) | ES2015 | 返回 rejected 的 Promise |
| Promise.all(iterable) | ES2015 | 等待所有 fulfilled |
| Promise.race(iterable) | ES2015 | 第一个 settled |
| Promise.allSettled(iterable) | ES2020 | 等待所有 settled |
| Promise.any(iterable) | ES2021 | 第一个 fulfilled |
| Promise.withResolvers() | ES2024 | 返回 { promise, resolve, reject } |
A.2 实例方法
| 方法 | 引入版本 | 用途 |
|---|---|---|
| promise.then(onFulfilled, onRejected) | ES2015 | 注册回调,返回新 Promise |
| promise.catch(onRejected) | ES2015 | then(undefined, onRejected) |
| promise.finally(onFinally) | ES2018 | settled 后执行,不改变值 |
A.3 内部插槽
| 插槽 | 用途 |
|---|---|
| [[PromiseState]] | pending / fulfilled / rejected |
| [[PromiseResult]] | value 或 reason |
| [[PromiseFulfillReactions]] | fulfilled 时的回调数组 |
| [[PromiseRejectReactions]] | rejected 时的回调数组 |
附录 B:状态机转换图
resolve(value)
pending ─────────────────► fulfilled
│ │
│ │
│ reject(reason) │ (不可转换)
│ │
▼ ▼
rejected ──────────────► (不可转换)
(不可转换)
转换规则:
- pending → fulfilled:resolve(value) 调用
- pending → rejected:reject(reason) 调用,或 executor 抛错
- fulfilled → 任何:不允许
- rejected → 任何:不允许
附录 C:微任务调度时序
时间轴
─────────────────────────────────────────────►
[同步代码] [微任务] [宏任务]
│ │ │
│ Promise.resolve().then(cb) │
│ ──► cb 入队 │
│ │
▼ ▼ ▼
console.log(1) cb() setTimeout
console.log(2) console.log(3) console.log(4)
console.log(end) (在下一轮)
附录 D:thenable 判定流程
resolve(x)
│
▼
x === this? ──── Yes ──► reject(TypeError)
│
No
│
▼
x 是 Promise? ── Yes ──► 采用 x 的状态
│
No
│
▼
x 是对象/函数? ── No ──► fulfill(x)
│
Yes
│
▼
try { then = x.then }
│
├─ 抛错 ──► reject(err)
│
▼
then 是函数? ── No ──► fulfill(x)
│
Yes
│
▼
调用 then.call(x, resolveY, rejectY)
│
├─ resolveY(y) ──► 递归 resolve(y)
├─ rejectY(r) ──► reject(r)
└─ 抛错 ──► reject(err)(若未调用 resolve/reject)
附录 E:并发原语对比
all: [p1, p2, p3]
│ │ │
▼ ▼ ▼
✓ ✓ ✓ ──► [v1, v2, v3]
✗ � ──► reject(r1) 立即
race: [p1, p2, p3]
│ │ │
▼ ▼ ▼
✓ ──────► resolve(v1) 立即(第一个 settled)
✗ ──────► reject(r1) 立即(第一个 settled)
allSettled: [p1, p2, p3]
│ │ │
▼ ▼ ▼
✓ ✗ ✓ ──► [{v1}, {r2}, {v3}]
any: [p1, p2, p3]
│ │ │
▼ ▼ ▼
✓ ──────► resolve(v1) 立即(第一个 fulfilled)
✗ ✗ ✗ ──► reject(AggregateError)
附录 F:常见错误对照表
| 错误 | 原因 | 解决 |
|---|---|---|
| TypeError: Chaining cycle detected | then 返回 this | 检查 then 返回值 |
| UnhandledPromiseRejection | 未 catch 的 rejection | 链尾加 catch |
| Pending forever | executor 未调用 resolve/reject | 检查所有路径 |
| PromiseResolveThenableJob 递归 | thenable.then 返回 thenable | 检查 thenable 链 |
| 微任务饥饿 | 大量 then 链 | 用同步代码或 setTimeout |
| 内存泄漏 | 长生命周期持有 Promise | 用完即清 |
附录 G:async/await 与 Promise 对照
| Promise 写法 | async/await 写法 |
|---|---|
| p.then(v => console.log(v)) | const v = await p; console.log(v) |
| p.catch(e => console.log(e)) | try { await p } catch (e) { … } |
| Promise.all([p1, p2]).then(…) | const [v1, v2] = await Promise.all([p1, p2]) |
| p.then(…).catch(…).finally(…) | try { … } catch { … } finally { … } |
| p.then(v => v + 1) | const v = await p; return v + 1 |
| Promise.resolve(42) | (async () => 42)() |
附录 H:Promise 调试技巧
H.1 Node.js 调试
node --inspect my-script.js
# 在 Chrome DevTools 中打开 chrome://inspect
H.2 捕获未处理 rejection
// Node.js
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled:', promise, 'reason:', reason);
});
// 浏览器
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled:', event.reason);
});
H.3 追踪 Promise 链
// 给每个 Promise 打标签
function tag(p, label) {
p.then(
(v) => console.log(`[${label}] fulfilled:`, v),
(e) => console.log(`[${label}] rejected:`, e)
);
return p;
}
tag(fetch('/api/users'), 'users')
.then((r) => r.json())
.then((users) => tag(fetch(`/api/orders/${users[0].id}`), 'orders'));
H.4 Chrome DevTools Promise 面板
Chrome DevTools 的 Application > Background Services > Promise Rejections 可记录所有未处理 rejection。
附录 I:Promise 与性能基准
简单基准测试(在 V8 中):
// 1. 创建 100 万个 Promise
console.time('create');
for (let i = 0; i < 1e6; i++) {
Promise.resolve(i);
}
console.timeEnd('create'); // ~150ms
// 2. 链式调用 100 万次
console.time('chain');
let p = Promise.resolve(0);
for (let i = 0; i < 1e6; i++) {
p = p.then((v) => v + 1);
}
p.then((v) => {
console.timeEnd('chain'); // 数秒
console.log('result:', v);
});
// 3. async/await 等价
console.time('await');
(async () => {
let v = 0;
for (let i = 0; i < 1e6; i++) {
v = await Promise.resolve(v + 1);
}
console.timeEnd('await'); // 数秒
console.log('result:', v);
})();
结论:Promise 创建快(~150ns/个),但链式调用慢(微任务开销),async/await 在 V8 优化后与 Promise 链接近。
附录 J:术语表
| 术语 | 英文 | 含义 |
|---|---|---|
| Promise | Promise | 表示异步操作最终结果的对象 |
| 执行器 | Executor | Promise 构造器接收的函数,接收 resolve/reject |
| resolve | Resolve | 将 Promise 从 pending 转为 fulfilled 的函数 |
| reject | Reject | 将 Promise 从 pending 转为 rejected 的函数 |
| settled | Settled | fulfilled 或 rejected,即非 pending |
| thenable | Thenable | 含 then 方法的对象或函数 |
| 鸭子类型 | Duck Typing | 不看类型,只看行为 |
| 微任务 | Microtask | 在当前 Job 结束后执行的延迟任务 |
| 宏任务 | Macrotask | 在事件循环中独立调度的任务 |
| 事件循环 | Event Loop | JavaScript 的并发模型 |
| 状态机 | Finite State Machine | 有限个状态与转换的数学模型 |
| Monad | Monad | 函数式编程中的抽象,提供链式组合 |
| Promise Job | PromiseJob | ECMAScript 规范定义的微任务 |
| 解析过程 | Resolution Procedure | Promise A+ 规范定义的 thenable 处理流程 |
| 单次转换 | One-shot | Promise 状态只能从 pending 转换一次 |
| 值穿透 | Value Penetration | then 的非函数参数导致值原样传递 |
| 错误冒泡 | Error Bubbling | 未处理的 rejection 沿链传播 |
| 并发原语 | Concurrency Primitive | all/race/allSettled/any 等组合 Promise 的方法 |
| AggregateError | AggregateError | Promise.any 中所有 rejected 时抛出的错误 |
| withResolvers | withResolvers | ES2024 新增,返回 { promise, resolve, reject } |
本文档基于 Promise A+ 规范、ECMAScript 2025、WHATWG HTML Living Standard 编写,涵盖 Promise 构造器的形式化定义、状态机模型、thenable 解析、微任务调度、并发原语、错误处理、生产级模式等内容,旨在作为 MIT/Stanford/CMU 级别的工程教学材料。如需进一步深入,请参阅参考文献与延伸阅读章节。