Promise A+ 规范
一个为 JS promises 开放的标准。
promise 表示一个异步操作的实际结果。与一个 promise 交互的主要方法是 promise 的 then 方法,then 方法接收两个回调函数,第一个回调会在 promise 兑现时被调用,用于接收 promise 的最终值,第二个回调会在 promise 失败时被调用,用于接收 promise 不能被兑现的原因。
本规范详细描述了 then 方法的行为,提供了一个可操作交互的基础,所有符合 Promise/A+ 规范的 promise 都可以依靠该基础来实现。因此这个规范被认为是非常稳定的,尽管 Promise/A+ 组织可能偶尔地通过一些较小的向后兼容的改变修订规范,来解决新发现的一些边界情况。但是,只有在经过仔细的考虑、讨论和测试后,我们才会集成大的或者向后不兼容的更改。
在历史角度上,Promises/A+ 澄清了早期 Promises/A proposal 中的行为条款,将其扩展到涵盖事实上的行为,并省略了未明确说明或有问题的部分。
本规范没有涉及如何创建、兑现或者拒绝 promise,而是选择专注于 then 方法,未来的工作中,我们可能会提及到这些话题。
术语
- "promise" 是一个包含
then方法且该方法符合规范指定行为的对象或者函数。 - "thenable" 是一个定义了
then方法的对象或者函数。 - "value" 是任意合法的 JavaScript 值(包括
undefined、thenable、promise)。 - "exception" 是一个由
throw语句抛出的值。 - "reason" 是一个解释了为何 promise 被拒绝的值。
要求
1. Promise 状态
一个 promise 必须是以下三种状态之一:pending、fulfilled、或者 rejected。
- 当 promise 状态是 pending 时:
- 可以转换为 fulfilled 或者 rejected。
- 当 promise 状态是 fulfilled 时:
- 不能转换为其他状态。
- 必须包含一个不可变的 value。
- 当 promise 状态是 rejected 时:
- 不能转换为其他状态。
- 必须包含一个不可变的 reason。
这里的"不可变"代表的是值的引用相同(即当 === 为真值时)。
2. then 方法
promise 必须提供 then 方法来访问当前的值或最终的原因。
promise 的 then 方法接受 2 个参数:
promise.then(onFulfilled, onRejected)
-
onFulfilled和onRejected都是可选参数:- 如果
onFulfilled不是函数则会被忽略。 - 如果
onRejected不是函数则会被忽略。
- 如果
-
如果
onFulfilled是一个函数:- 它必须在
promise状态转换为 fulfilled 后调用,promise的结果作为该函数的第一个参数。 - 它不能在
promise被 fulfilled 前被调用。 - 它不能被调用超过一次。
- 它必须在
-
如果
onRejected是一个函数:- 它必须在
promise状态转换为 rejected 后调用,rejectedpromise的拒绝原因作为该函数的第一个参数。 - 它不能在
promise被 rejected 前被调用。 - 它不能被调用超过一次。
- 它必须在
-
onFulfilled或onRejected不能被调用直到执行上下文栈仅包含平台代码时才能被调用。 -
onFulfilled和onRejected必须作为函数被调用(即没有this值)。 -
then对于同一个 promise,可能会被多次调用:- 如果/当
promise是 fulfilled,所有onFulfilled回调函数必须按照最初调用then的顺序执行。 - 如果/当
promise是 rejected,所有onRejected回调函数必须按照最初调用then的顺序执行。
- 如果/当
-
then方法必须返回一个 promise:promise2 = promise1.then(onFulfilled, onRejected)- 如果
onFulfilled或者onRejected返回了一个值x,则执行[[Resolve]](promise2, x)解决过程(详见下文)。 - 如果
onFulfilled或onRejected抛出一个异常e,promise2必须使用e作为转为失败状态的原因。 - 如果
onFulfilled不是一个函数,并且promise1已经兑现,promise2也必须转为兑现状态,同时使用promise1的兑现值作为兑现值。 - 如果
onRejected不是一个函数,并且promise1已经转为失败状态,promise2也必须转为失败状态,同时使用promise1的失败原因作为失败原因。
- 如果
3. Promise 解决过程
Promise 解决过程 是一个抽象的处理输入的 promise 和 x 的操作,表示为 [[Resolve]](promise, x)。如果 x 是一个 thenable,则尝试让 promise 转换为 x 的状态,假设 x 的行为至少有点像 promise。否则将使用 x 作为 promise fulfilled/rejected 的值进行处理。
这种 thenable 的特性使得 Promise 的实现更具有通用性,只要它们暴露了一个符合 Promise/A+ 规范的 then 方法,这同时也使得遵循 Promise/A+ 规范的实现可以与那些不太规范的实现能共存。
要执行 [[Resolve]](promise, x) 操作,需要执行以下步骤:
- 如果
promise和x是同一个引用,使用TypeError作为理由拒绝promise。 - 如果
x是一个 promise,则采用它的状态:- 如果
x是 pending,promise必须保持 pending 直到x被 fulfilled 或者被 rejected。 - 如果/当
x是 fulfilled,使用x的值兑现promise。 - 如果/当
x是 rejected,使用x的值拒绝promise。
- 如果
- 否则,如果
x是对象或者函数:- 尝试将
x.then储存至新变量then(then 保持对x.then的引用)。 - 如果访问
x.then时收到异常e,使用e拒绝promise。 - 如果
then是一个函数,将x作为this调用then方法,且第一个参数为resolvePromise,第二个参数为rejectPromise,这里:- 如果
resolvePromise以y作为第一个参数被回调,执行[[Resolve]](promise, y)。 - 如果/当
rejectPromise以r作为拒绝原因被回调,则用r拒绝promise。 - 如果
resolvePromise和rejectPromise都被调用,或者同一被多次调用,那么第一次调用优先,以后的调用都会被忽略。 - 如果调用中
then抛出了一个异常e:- 如果
resolvePromise或者rejectPromise已经被调用,则忽略。 - 否则,用
e作为原因,拒绝promise。
- 如果
- 如果
- 如果
then不是一个函数,用x兑现promise。
- 尝试将
- 如果
x不是一个函数或者对象,用x兑现promise。
即使 promise 已经在一个循环 thenable 链里被 resolve,由于 [[Resolve]](promise, thenable) 的递归特性,最终将导致 [[Resolve]](promise, thenable) 被再次调用,遵循上面的算法会导致无限递归。规范中并不要求强制处理,但是鼓励实现者检测递归并且用一个具有丰富信息的 TypeError 拒绝 promise。
说明
- "平台代码"指的是引擎、环境以及 promise 实现代码。实际上,这个要求确保了
onFulfilled和onRejected异步执行,当then方法被调用后,事件循环队列有了新的任务。这种特性具体实现可以使用宏任务机制,例如setTimeout或setImmediate,或者微任务机制,例如MutationObserver或process.nextTick。由于考虑了 promise 在平台的实现,所以它本身可能包含一个任务调度队列来调用回调函数。 - 在严格模式下,该函数的
this将是undefined;在传统模式下,this将是全局对象。 - 如果实现满足规范所有要求,则可允许
promise2 === promise1,但每一个实现都必须明确指出promise2 === promise1会在怎样的条件下出现。 - 通常,只有符合本规范的 promise 才是真正的 promise。此条款允许使用实现特定的手段来采用已知合规的 promise 的状态。
- 首先存储对
x.then这一引用,然后测试引用是否有效,然后调用该引用,避免了多次访问x.then属性,这种预防措施对于确保访问器属性的一致性非常重要,因为访问器属性的值可能在两次访问中改变。 - 实现不应该限制 thenable 链的深度。只有真正的死循环才应该引发一个
TypeError;如果遇到了一个明确的截然不同的无限调用链,那么无限执行下去才是正确的行为。
代码
实现
/**
* 符合 Promise/A+ 规范的 Promise 实现
*
* 核心要点:
* 1. 状态只能从 pending → fulfilled 或 pending → rejected,且不可逆
* 2. then 返回一个新的 Promise,支持链式调用
* 3. 支持值穿透(onFulfilled/onRejected 非函数时透传 value/reason)
* 4. 回调异步执行(通过微任务 queueMicrotask 调度)
* 5. 错误捕获(executor 与 then 回调中的异常都会被捕获)
*/
const PENDING = 'pending' as const;
const FULFILLED = 'fulfilled' as const;
const REJECTED = 'rejected' as const;
type PromiseState = typeof PENDING | typeof FULFILLED | typeof REJECTED;
type Executor<T> = (
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void,
) => void;
type OnFulfilled<T, R> = ((value: T) => R | PromiseLike<R>) | null | undefined;
type OnRejected<R> = ((reason: unknown) => R | PromiseLike<R>) | null | undefined;
/**
* 解析 then 回调的返回值,处理 thenable 与循环引用
*/
function resolvePromise<T>(
promise: PromiseAPlus<T>,
result: unknown,
resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void,
): void {
// 2.3.1 如果 result 和 promise 相同,抛 TypeError 防止循环引用
if (result === promise) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
// 2.3.2 如果 result 是 Promise/A+ 实例
if (result instanceof PromiseAPlus) {
// 2.3.2.3 如果 result 仍处于 pending,则等待
if (result.state === PENDING) {
(result as PromiseAPlus<T>).then(
(v: T) => resolvePromise(promise, v, resolve, reject),
(r: unknown) => reject(r),
);
} else if (result.state === FULFILLED) {
resolve(result.value as T);
} else {
reject(result.reason);
}
return;
}
// 2.3.3 如果 result 是 thenable 对象/函数
if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
let then: unknown;
try {
then = (result as { then?: unknown; }).then;
} catch (e) {
// 2.3.3.2 取 then 抛错则直接 reject
reject(e);
return;
}
// 2.3.3.3 如果 then 是函数,则以 result 为 this 调用
if (typeof then === 'function') {
let called = false;
try {
(then as (
res: (v: unknown) => void,
rej: (r: unknown) => void,
) => void).call(
result,
(v: unknown) => {
if (called) return;
called = true;
resolvePromise(promise, v, resolve, reject);
},
(r: unknown) => {
if (called) return;
called = true;
reject(r);
},
);
} catch (e) {
// 2.3.3.3.4 如果已调用过 then 的回调,则忽略
if (called) return;
called = true;
reject(e);
}
return;
}
}
// 2.3.4 普通值,直接 resolve
resolve(result as T | PromiseLike<T>);
}
export class PromiseAPlus<T> implements PromiseLike<T> {
private _state: PromiseState = PENDING;
private _value!: T;
private _reason!: unknown;
// 回调队列:pending 期间收集,状态确定后异步执行
private readonly _onFulfilledCallbacks: Array<() => void> = [];
private readonly _onRejectedCallbacks: Array<() => void> = [];
constructor(executor: Executor<T>) {
const resolve = (value: T | PromiseLike<T>): void => {
if (this._state !== PENDING) return;
// 2.3 统一通过 resolvePromise 处理,支持 PromiseAPlus 与通用 thenable
resolvePromise(
this as unknown as PromiseAPlus<unknown>,
value,
(v: unknown) => this._doResolve(v as T),
(r: unknown) => this._doReject(r),
);
};
const reject = (reason?: unknown): void => {
if (this._state !== PENDING) return;
this._doReject(reason);
};
try {
executor(resolve, reject);
} catch (e) {
// executor 抛异常直接 reject(前提是尚未确定状态)
reject(e);
}
}
private _doResolve(value: T): void {
if (this._state !== PENDING) return;
this._state = FULFILLED;
this._value = value;
// 2.2.4 异步执行回调
for (const cb of this._onFulfilledCallbacks) {
queueMicrotask(cb);
}
this._onFulfilledCallbacks.length = 0;
this._onRejectedCallbacks.length = 0;
}
private _doReject(reason: unknown): void {
if (this._state !== PENDING) return;
this._state = REJECTED;
this._reason = reason;
for (const cb of this._onRejectedCallbacks) {
queueMicrotask(cb);
}
this._onFulfilledCallbacks.length = 0;
this._onRejectedCallbacks.length = 0;
}
get state(): PromiseState {
return this._state;
}
get value(): T {
return this._value;
}
get reason(): unknown {
return this._reason;
}
then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null | undefined,
): PromiseAPlus<TResult1 | TResult2> {
const promise2 = new PromiseAPlus<TResult1 | TResult2>((resolve, reject) => {
const handleFulfilled = (): void => {
try {
if (typeof onFulfilled !== 'function') {
// 2.2.7.3 值穿透
resolve(this._value as unknown as TResult1 | TResult2 | PromiseLike<TResult1 | TResult2>);
} else {
const x = onFulfilled(this._value);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
};
const handleRejected = (): void => {
try {
if (typeof onRejected !== 'function') {
// 2.2.7.4 错误透传
reject(this._reason);
} else {
const x = onRejected(this._reason);
resolvePromise(promise2, x, resolve, reject);
}
} catch (e) {
reject(e);
}
};
if (this._state === FULFILLED) {
// 2.2.4 已确定状态,异步执行
queueMicrotask(handleFulfilled);
} else if (this._state === REJECTED) {
queueMicrotask(handleRejected);
} else {
// pending:先入队,待 resolve/reject 时触发
this._onFulfilledCallbacks.push(handleFulfilled);
this._onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
catch<TResult = T>(onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null | undefined): PromiseAPlus<T | TResult> {
return this.then(undefined, onRejected);
}
finally(onFinally?: () => void): PromiseAPlus<T> {
return this.then<T, never>(
(value: T): T | PromiseLike<T> =>
PromiseAPlus.resolve(
typeof onFinally === 'function' ? onFinally() : undefined,
).then(() => value) as unknown as T | PromiseLike<T>,
(reason: unknown): never | PromiseLike<never> =>
PromiseAPlus.resolve(
typeof onFinally === 'function' ? onFinally() : undefined,
).then(() => {
throw reason;
}) as unknown as never | PromiseLike<never>,
) as PromiseAPlus<T>;
}
static resolve(): PromiseAPlus<void>;
static resolve<U>(value: U | PromiseLike<U>): PromiseAPlus<U>;
static resolve<U>(value?: U | PromiseLike<U>): PromiseAPlus<U> {
if (value instanceof PromiseAPlus) {
return value as PromiseAPlus<U>;
}
return new PromiseAPlus<U>((resolve) => resolve(value as U));
}
static reject<U = never>(reason?: unknown): PromiseAPlus<U> {
return new PromiseAPlus<U>((_, reject) => reject(reason));
}
static all<U>(promises: Iterable<U | PromiseLike<U>>): PromiseAPlus<U[]> {
return new PromiseAPlus<U[]>((resolve, reject) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
return;
}
const result: U[] = new Array(arr.length);
let remaining = arr.length;
arr.forEach((p, index) => {
PromiseAPlus.resolve(p).then(
(v: U) => {
result[index] = v;
if (--remaining === 0) resolve(result);
},
(r: unknown) => reject(r),
);
});
});
}
static race<U>(promises: Iterable<U | PromiseLike<U>>): PromiseAPlus<U> {
return new PromiseAPlus<U>((resolve, reject) => {
const arr = Array.from(promises);
arr.forEach((p) => {
PromiseAPlus.resolve(p).then(
(v: U) => resolve(v),
(r: unknown) => reject(r),
);
});
});
}
static allSettled<U>(
promises: Iterable<U | PromiseLike<U>>,
): PromiseAPlus<Array<{ status: 'fulfilled'; value: U; } | { status: 'rejected'; reason: unknown; }>> {
return new PromiseAPlus((resolve) => {
const arr = Array.from(promises);
if (arr.length === 0) {
resolve([]);
return;
}
const result: Array<{ status: 'fulfilled'; value: U; } | { status: 'rejected'; reason: unknown; }> = new Array(arr.length);
let remaining = arr.length;
arr.forEach((p, index) => {
PromiseAPlus.resolve(p).then(
(v: U) => {
result[index] = { status: 'fulfilled', value: v };
if (--remaining === 0) resolve(result);
},
(r: unknown) => {
result[index] = { status: 'rejected', reason: r };
if (--remaining === 0) resolve(result);
},
);
});
});
}
// Promise/A+ 测试适配器所需
static deferred<U>() {
let resolve!: (value: U | PromiseLike<U>) => void;
let reject!: (reason?: unknown) => void;
const promise = new PromiseAPlus<U>((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
}
测试
import { describe, it, expect, vi } from 'vitest'
import { PromiseAPlus } from './promise-aplus'
describe('PromiseAPlus — Promise/A+ 规范', () => {
// ============================================================
// 2.1 Promise 状态
// ============================================================
describe('2.1 Promise 状态', () => {
describe('2.1.1 pending', () => {
it('2.1.1.1 pending 可以转换为 fulfilled', () => {
const p = new PromiseAPlus<number>((resolve) => resolve(1))
return p.then((v) => {
expect(v).toBe(1)
expect(p.state).toBe('fulfilled')
})
})
it('2.1.1.1 pending 可以转换为 rejected', () => {
const p = new PromiseAPlus<number>((_, reject) => reject(new Error('err')))
return p.then(
() => expect.fail('should not be fulfilled'),
(e) => {
expect((e as Error).message).toBe('err')
expect(p.state).toBe('rejected')
},
)
})
})
describe('2.1.2 fulfilled', () => {
it('2.1.2.1 fulfilled 后不能再转换为其他状态', () => {
const p = new PromiseAPlus<number>((resolve, reject) => {
resolve(1)
reject(new Error('should be ignored'))
resolve(2)
})
return p.then(
(v) => expect(v).toBe(1),
() => expect.fail('should not be rejected'),
)
})
it('2.1.2.2 fulfilled 后必须有一个不可变的 value', () => {
const obj = { a: 1 }
const p = new PromiseAPlus<typeof obj>((resolve) => resolve(obj))
return p.then((v) => {
expect(v).toBe(obj) // 引用相同
})
})
})
describe('2.1.3 rejected', () => {
it('2.1.3.1 rejected 后不能再转换为其他状态', () => {
const p = new PromiseAPlus<number>((resolve, reject) => {
reject(new Error('first'))
resolve(1)
reject(new Error('second'))
})
return p.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('first'),
)
})
it('2.1.3.2 rejected 后必须有一个不可变的 reason', () => {
const err = new Error('reason')
const p = new PromiseAPlus<number>((_, reject) => reject(err))
return p.then(
() => expect.fail('should not be fulfilled'),
(e) => expect(e).toBe(err), // 引用相同
)
})
})
})
// ============================================================
// 2.2 then 方法
// ============================================================
describe('2.2 then 方法', () => {
describe('2.2.1 onFulfilled 和 onRejected 是可选参数', () => {
it('2.2.1.1 onFulfilled 不是函数时被忽略(值穿透)', () => {
return PromiseAPlus.resolve(1)
.then(null as never)
.then('not a function' as unknown as never)
.then((v) => expect(v).toBe(1))
})
it('2.2.1.2 onRejected 不是函数时被忽略(错误透传)', () => {
return PromiseAPlus.reject(new Error('pass'))
.then(undefined, null as never)
.then(undefined, 'not a function' as unknown as never)
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('pass'),
)
})
})
describe('2.2.2 onFulfilled', () => {
it('2.2.2.1 在 promise fulfilled 后以 value 为第一个参数调用', () => {
return PromiseAPlus.resolve(42).then((v) => expect(v).toBe(42))
})
it('2.2.2.2 不能在 promise fulfilled 前被调用', () => {
let called = false
const p = new PromiseAPlus<number>((resolve) => {
setTimeout(() => resolve(1), 10)
})
p.then(() => {
called = true
})
return Promise.resolve().then(() => {
expect(called).toBe(false) // 此时 promise 尚未 resolve
})
})
it('2.2.2.3 不能被调用超过一次', () => {
const spy = vi.fn()
const p = new PromiseAPlus<number>((resolve) => resolve(1))
p.then(spy)
return Promise.resolve().then(() => {
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
describe('2.2.3 onRejected', () => {
it('2.2.3.1 在 promise rejected 后以 reason 为第一个参数调用', () => {
return PromiseAPlus.reject(new Error('bad')).then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('bad'),
)
})
it('2.2.3.2 不能在 promise rejected 前被调用', () => {
let called = false
const p = new PromiseAPlus<number>((_, reject) => {
setTimeout(() => reject(new Error('err')), 10)
})
p.then(undefined, () => {
called = true
})
return Promise.resolve().then(() => {
expect(called).toBe(false)
})
})
it('2.2.3.3 不能被调用超过一次', () => {
const spy = vi.fn()
const p = new PromiseAPlus<number>((_, reject) => reject(new Error('err')))
p.then(undefined, spy)
return Promise.resolve().then(() => {
expect(spy).toHaveBeenCalledTimes(1)
})
})
})
describe('2.2.4 onFulfilled/onRejected 异步执行', () => {
it('2.2.4 不能在执行上下文栈仅包含平台代码前被调用', () => {
const order: string[] = []
const p = PromiseAPlus.resolve(1)
p.then(() => order.push('then'))
order.push('sync')
return Promise.resolve().then(() => {
expect(order).toEqual(['sync', 'then'])
})
})
it('2.2.4 rejected 回调也异步执行', () => {
const order: string[] = []
const p = PromiseAPlus.reject(new Error('err'))
p.then(undefined, () => order.push('catch'))
order.push('sync')
return Promise.resolve().then(() => {
expect(order).toEqual(['sync', 'catch'])
})
})
})
describe('2.2.5 onFulfilled/onRejected 作为函数调用(无 this)', () => {
it('2.2.5 在严格模式下 this 为 undefined', () => {
let capturedThis: unknown = 'initial'
return PromiseAPlus.resolve(1)
.then(function (this: unknown) {
capturedThis = this
})
.then(() => expect(capturedThis).toBeUndefined())
})
})
describe('2.2.6 then 可被多次调用', () => {
it('2.2.6.1 多个 onFulfilled 按注册顺序执行', () => {
const order: string[] = []
const p = PromiseAPlus.resolve(1)
p.then(() => order.push('a'))
p.then(() => order.push('b'))
p.then(() => order.push('c'))
return Promise.resolve().then(() => {
expect(order).toEqual(['a', 'b', 'c'])
})
})
it('2.2.6.2 多个 onRejected 按注册顺序执行', () => {
const order: string[] = []
const p = PromiseAPlus.reject(new Error('err'))
p.then(undefined, () => order.push('a'))
p.then(undefined, () => order.push('b'))
p.then(undefined, () => order.push('c'))
return Promise.resolve().then(() => {
expect(order).toEqual(['a', 'b', 'c'])
})
})
it('2.2.6 pending 状态下多个回调按注册顺序执行', () => {
const order: number[] = []
const p = new PromiseAPlus<number>((resolve) => {
setTimeout(() => resolve(1), 10)
})
p.then(() => order.push(1))
p.then(() => order.push(2))
p.then(() => order.push(3))
return p.then(() => {
// 等待所有微任务执行完
return Promise.resolve().then(() => {
expect(order).toEqual([1, 2, 3])
})
})
})
})
describe('2.2.7 then 必须返回一个 promise', () => {
it('2.2.7 then 返回的值是 PromiseAPlus 实例且不等于原 promise', () => {
const p1 = PromiseAPlus.resolve(1)
const p2 = p1.then(() => 2)
expect(p2).not.toBe(p1)
expect(p2).toBeInstanceOf(PromiseAPlus)
})
describe('2.2.7.1 onFulfilled/onRejected 返回值 x 执行 [[Resolve]]', () => {
it('返回普通值', () => {
return PromiseAPlus.resolve(1)
.then(() => 2)
.then((v) => expect(v).toBe(2))
})
it('返回 null', () => {
return PromiseAPlus.resolve(1)
.then(() => null)
.then((v) => expect(v).toBeNull())
})
it('返回 undefined', () => {
return PromiseAPlus.resolve(1)
.then(() => undefined)
.then((v) => expect(v).toBeUndefined())
})
it('返回对象', () => {
const obj = { a: 1 }
return PromiseAPlus.resolve(1)
.then(() => obj)
.then((v) => expect(v).toBe(obj))
})
it('返回另一个 PromiseAPlus(fulfilled)', () => {
return PromiseAPlus.resolve(1)
.then(() => PromiseAPlus.resolve(3))
.then((v) => expect(v).toBe(3))
})
it('返回另一个 PromiseAPlus(rejected)', () => {
return PromiseAPlus.resolve(1)
.then(() => PromiseAPlus.reject(new Error('nested')))
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('nested'),
)
})
it('返回 thenable 对象', () => {
return PromiseAPlus.resolve(1)
.then((v) => ({
then: (resolve?: ((n: number) => void) | null) => resolve!(v + 10),
} as PromiseLike<number>))
.then((v) => expect(v).toBe(11))
})
it('onRejected 返回值也会被 resolve', () => {
return PromiseAPlus.reject(new Error('err'))
.then(undefined, () => 'recovered')
.then((v) => expect(v).toBe('recovered'))
})
})
describe('2.2.7.2 onFulfilled/onRejected 抛异常 e,promise2 reject(e)', () => {
it('onFulfilled 抛异常', () => {
return PromiseAPlus.resolve(1)
.then(() => {
throw new Error('throw')
})
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('throw'),
)
})
it('onRejected 抛异常', () => {
return PromiseAPlus.reject(new Error('first'))
.then(undefined, () => {
throw new Error('second')
})
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('second'),
)
})
})
describe('2.2.7.3 onFulfilled 非函数时 promise2 透传 value', () => {
it('值穿透 fulfilled', () => {
return PromiseAPlus.resolve(42)
.then(null as never)
.then((v) => expect(v).toBe(42))
})
it('值穿透多层', () => {
return PromiseAPlus.resolve(42)
.then(null)
.then(undefined)
.then('x' as unknown as never)
.then((v) => expect(v).toBe(42))
})
})
describe('2.2.7.4 onRejected 非函数时 promise2 透传 reason', () => {
it('错误透传 rejected', () => {
return PromiseAPlus.reject(new Error('pass'))
.then(undefined, null as never)
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('pass'),
)
})
it('错误透传多层', () => {
return PromiseAPlus.reject(new Error('pass'))
.then(undefined, undefined)
.then(undefined, null)
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('pass'),
)
})
})
})
})
// ============================================================
// 2.3 Promise 解决过程 [[Resolve]](promise, x)
// ============================================================
describe('2.3 Promise 解决过程', () => {
describe('2.3.1 promise === x,TypeError 拒绝', () => {
it('返回自身 promise 导致循环引用', () => {
const p = PromiseAPlus.resolve(1)
const p2: PromiseAPlus<number> = p.then(() => p2)
return p2.then(
() => expect.fail('should not be fulfilled'),
(e) => expect(e).toBeInstanceOf(TypeError),
)
})
})
describe('2.3.2 x 是 promise,采用其状态', () => {
it('2.3.2.1 x 为 pending,promise 保持 pending', async () => {
const inner = new PromiseAPlus<number>((resolve) => {
setTimeout(() => resolve(1), 10)
})
const p2 = PromiseAPlus.resolve(1).then(() => inner)
// 立即检查应为 pending
await Promise.resolve()
expect(p2.state).toBe('pending')
return p2.then((v) => expect(v).toBe(1))
})
it('2.3.2.2 x 为 fulfilled,promise 以 x 的 value fulfilled', () => {
return PromiseAPlus.resolve(1)
.then(() => PromiseAPlus.resolve(99))
.then((v) => expect(v).toBe(99))
})
it('2.3.2.3 x 为 rejected,promise 以 x 的 reason rejected', () => {
return PromiseAPlus.resolve(1)
.then(() => PromiseAPlus.reject(new Error('rej')))
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('rej'),
)
})
})
describe('2.3.3 x 是对象或函数', () => {
it('2.3.3.2 读取 x.then 抛异常,以该异常 reject', () => {
const obj = {}
Object.defineProperty(obj, 'then', {
get() {
throw new Error('get then error')
},
})
return PromiseAPlus.resolve(1)
.then(() => obj as PromiseLike<unknown>)
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('get then error'),
)
})
describe('2.3.3.3 then 是函数', () => {
it('2.3.3.3.1 resolvePromise(y) 递归解决', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: (resolve: (v: unknown) => void) =>
resolve({ then: (r: (v: unknown) => void) => r(42) }),
} as PromiseLike<unknown>))
.then((v) => expect(v).toBe(42))
})
it('2.3.3.3.2 rejectPromise(r) 直接拒绝', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: (_resolve: unknown, reject: (r: unknown) => void) =>
reject(new Error('thenable reject')),
} as PromiseLike<unknown>))
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('thenable reject'),
)
})
it('2.3.3.3.3 resolvePromise 和 rejectPromise 都调用,首次优先', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: (
resolve: (v: unknown) => void,
reject: (r: unknown) => void,
) => {
resolve(1)
reject(new Error('should be ignored'))
resolve(2)
},
} as PromiseLike<unknown>))
.then((v) => expect(v).toBe(1))
})
it('2.3.3.3.3 reject 先调用后 resolve 被忽略', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: (
resolve: (v: unknown) => void,
reject: (r: unknown) => void,
) => {
reject(new Error('first'))
resolve(2)
reject(new Error('second'))
},
} as PromiseLike<unknown>))
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('first'),
)
})
it('2.3.3.3.4 then 抛异常但 resolvePromise 已调用则忽略', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: (resolve: (v: unknown) => void) => {
resolve(42)
throw new Error('should be ignored')
},
} as PromiseLike<unknown>))
.then((v) => expect(v).toBe(42))
})
it('2.3.3.3.4 then 抛异常且未调用回调,以异常 reject', () => {
return PromiseAPlus.resolve(1)
.then(() => ({
then: () => {
throw new Error('then throws')
},
} as PromiseLike<unknown>))
.then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('then throws'),
)
})
it('2.3.3.3 then 以 x 为 this 调用', () => {
const thenable = {
label: 'I am x',
then(this: { label: string }, resolve: (v: unknown) => void) {
resolve(this.label)
},
}
return PromiseAPlus.resolve(1)
.then(() => thenable as unknown as PromiseLike<unknown>)
.then((v) => expect(v).toBe('I am x'))
})
})
it('2.3.3.4 then 不是函数,以 x 兑现', () => {
return PromiseAPlus.resolve(1)
.then(() => ({ then: 'not a function', a: 1 }))
.then((v) => expect(v).toEqual({ then: 'not a function', a: 1 }))
})
it('2.3.3 x 是函数(非 thenable)以 x 兑现', () => {
const fn = () => 42
return PromiseAPlus.resolve(1)
.then(() => fn)
.then((v) => expect(v).toBe(fn))
})
})
describe('2.3.4 x 不是对象或函数,以 x 兑现', () => {
it('x 是数字', () => {
return PromiseAPlus.resolve(1)
.then(() => 123)
.then((v) => expect(v).toBe(123))
})
it('x 是字符串', () => {
return PromiseAPlus.resolve(1)
.then(() => 'hello')
.then((v) => expect(v).toBe('hello'))
})
it('x 是 boolean', () => {
return PromiseAPlus.resolve(1)
.then(() => true)
.then((v) => expect(v).toBe(true))
})
it('x 是 null', () => {
return PromiseAPlus.resolve(1)
.then(() => null)
.then((v) => expect(v).toBeNull())
})
it('x 是 undefined', () => {
return PromiseAPlus.resolve(1)
.then(() => undefined)
.then((v) => expect(v).toBeUndefined())
})
})
})
// ============================================================
// 补充:executor 错误捕获 & 静态方法
// ============================================================
describe('executor 错误捕获', () => {
it('executor 抛出同步异常被捕获为 rejected', () => {
return new PromiseAPlus<number>(() => {
throw new Error('boom')
}).then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('boom'),
)
})
it('executor resolve 后抛异常被忽略', () => {
return new PromiseAPlus<number>((resolve) => {
resolve(1)
throw new Error('ignored')
}).then((v) => expect(v).toBe(1))
})
})
describe('静态方法', () => {
it('resolve 无参数返回 undefined', () => {
return PromiseAPlus.resolve().then((v) => expect(v).toBeUndefined())
})
it('resolve 传入 thenable 会被展开', () => {
return PromiseAPlus.resolve({
then: (resolve: (v: number) => void) => resolve(42),
} as PromiseLike<number>).then((v) => expect(v).toBe(42))
})
it('all 全部成功', () => {
return PromiseAPlus.all([PromiseAPlus.resolve(1), PromiseAPlus.resolve(2), 3]).then(
(arr) => expect(arr).toEqual([1, 2, 3]),
)
})
it('all 有失败则整体失败', () => {
return PromiseAPlus.all([
PromiseAPlus.resolve(1),
PromiseAPlus.reject(new Error('fail')),
]).then(
() => expect.fail('should not be fulfilled'),
(e) => expect((e as Error).message).toBe('fail'),
)
})
it('all 空数组立即 resolve', () => {
return PromiseAPlus.all([]).then((arr) => expect(arr).toEqual([]))
})
it('race 返回第一个完成的结果', () => {
return PromiseAPlus.race([
new PromiseAPlus<number>((resolve) => setTimeout(() => resolve(2), 20)),
PromiseAPlus.resolve(1),
]).then((v) => expect(v).toBe(1))
})
it('allSettled 等待全部完成', () => {
return PromiseAPlus.allSettled([
PromiseAPlus.resolve(1),
PromiseAPlus.reject(new Error('x')),
]).then((results) => {
expect(results).toEqual([
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: new Error('x') },
])
})
})
})
describe('finally', () => {
it('fulfilled 时执行 finally 并透传值', () => {
let called = false
return PromiseAPlus.resolve(1)
.finally(() => {
called = true
})
.then((v) => {
expect(called).toBe(true)
expect(v).toBe(1)
})
})
it('rejected 时执行 finally 并透传错误', () => {
let called = false
return PromiseAPlus.reject(new Error('err'))
.finally(() => {
called = true
})
.then(
() => expect.fail('should not be fulfilled'),
(e) => {
expect(called).toBe(true)
expect((e as Error).message).toBe('err')
},
)
})
})
})