更新日志:
- 精简非完整代码示例(只保留 any 示例, 其它静态方法在完整代码示例里); 修正静态方法:
any
在可迭代对象(如数组长度为 0)无任何迭代项时根据规范应该返回错误; 调整AggregateError 异常抛出的错误消息格式; 添加新的静态方法:withResolvers()
(2024-01-27 03:09) - 修正一些错误拼写(2023-08-16 23:17)...
- 添加静态方法
ToyPromise.resolve(aPromise)
返回幂等性说明, 更新文章标注(2023-08-28 02:44)
出发点
- 围绕着手写 Promise 规范实现, 大家都喜欢一人写一个, 趁着假期摸鱼时间以及水友群也有提到, 我也参考多位大佬们已经总结过的自己也模仿(
抄) 写出一个, 在此满足规范实现的基础上, 做一些不是那么规范的事情♂️
- 文章不会从头到尾再总结一遍, 因为有很多人已经总结的很好了, 所以尽可能省流, 而不复读机
- PS: 实际业务中这么整的意义是啥我还暂时不清楚(
木有工作 🥲...) - 会提到一些可能没怎么被注意到的
.all, .race. allSettled
这些方法的一些行为(如一个代理数组) - 不少 ts 我直接 copy 的 ts 标准内置声明(
没毛病) - 以此屑文抛砖引玉, 如有错误, 还请指出(轻喷 😶🌫️)
置顶的参考链接
省流说明
- 建议直接拉到后面看实现代码(胜过我这里写的不咋的文笔)
- 详细的分析这里不会再写一遍, 可以参考我参考的链接 大佬们写的已经非常详细
- Promise 的内部状态可以简约概括为一个状态机: 可以从
pending
到其它两个状态, 且一状态落地(fulfilled, rejected
), 就不能更改, 规范定义了三种类型, 但这里为了好玩些, 所以不妨加个aborted
, 可以得到如下一个简单的枚举定义(aborted 末尾使用)enum ToyPromiseStatus { PENDING = "pending", FULFILLED = "fulfilled", REJECTED = "rejected", ABORTED = "aborted" }
- Promise 的构造器是一个函数, 内部的两个
functor
(即 resolve, reject) 由 Promise 内部提供个 executor 使用(描述不佳, 建议直接看代码), 如名称一般, 可以设置 promise 的状态和内部值(value/reason
) then
的调用相当于返回一个新的Promise
实例return new ToyPromise(...)
, 并根据落地/待定
的状况来决定处理方式, 如果已经落地, 直接解决 (即如果 executor 里的 落地方法没放在 setTimeout 等的话, 那么状态已经被改变, 在 then 里取得时已经是落地状态), 如果是 pending, 那么表明 executor 里执行 resolve/reject 可能放在类似 setTimeout 的异步包装里, 此时可在内部设置两个队列分别保存两种可能触发的事件, 等 executor 里异步包装的 resolve/reject 执行时机到来后, 各自从队列里取出执行(当然, 谁先执行, 就代表状态落地), 且 then 的onFulfilled
里返回的值可以在下一个 then 里获取, 且如果是 Thenable, 那么需要递归处理, 等待完成获取- 具体的微任务实现方式使用:
queueMicrotask(VoidFunction)
模拟 - catch, finally 实际上是预先签名版本(提前设置 then 的参数)
- ...
代码
一个基础实现的自定义 Promise
-
描述过于枯燥, 先看一个基础实现版(PS: 借鉴了参考文章里的代码实现和 ts 内置标准类声明)
enum ToyPromiseStatus { PENDING = "pending", FULFILLED = "fulfilled", REJECTED = "rejected" } type ToyPromiseAwaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? F extends (value: infer V, ...args: infer _) => any ? ToyPromiseAwaited<V> : never : T; interface Thenable<T> { then<TResult1 = T, TResult2 = never>( onFulfilled?: ((value: T) => TResult1 | Thenable<TResult1>) | undefined | null, onRejected?: ((reason: any) => TResult2 | Thenable<TResult2>) | undefined | null ): Thenable<TResult1 | TResult2>; } type ExecutorType<T> = (resolve: (value: T) => void, reject: (reason: any) => void) => void; type ToyPromiseAllSettledResult<T> = | { status: "fulfilled"; value: T } | { status: "rejected"; reason: any; }; class ToyPromise<T> implements Thenable<T> { #state: ToyPromiseStatus; #value!: T; #reason: any; readonly #resolvedCallbacks: Array<(value: T) => void>; readonly #rejectedCallbacks: Array<(reason: any) => void>; public constructor(executor: ExecutorType<T>) { if (typeof executor !== "function") { throw new TypeError(`ToyPromise resolver #<${typeof executor}> is not a function`); } this.#state = ToyPromiseStatus.PENDING; this.#resolvedCallbacks = []; this.#rejectedCallbacks = []; try { executor(this.#resolve.bind(this), this.#reject.bind(this)); } catch (reason: any) { this.#reject(reason); } } #resolve(value: T): void { // 处理循环引用 if (<ToyPromise<T>>value === this) { this.#reject(new TypeError("Chaining cycle detected for promise #<OptionalPromise>")); return; } if (value instanceof Promise || value instanceof ToyPromise) { (<Thenable<T>>value).then(this.#resolve.bind(this), this.#reject.bind(this)); return; } if (this.#state === ToyPromiseStatus.PENDING) { this.#value = value; this.#state = ToyPromiseStatus.FULFILLED; for (const cb of this.#resolvedCallbacks) { cb(this.#value); } } } #reject(reason: any): void { if (this.#state === ToyPromiseStatus.PENDING) { this.#reason = reason; this.#state = ToyPromiseStatus.REJECTED; for (const cb of this.#rejectedCallbacks) { cb(this.#reason); } } } static #processThenableOrNor<T>( promise: ToyPromise<T>, value: any, resolve: (value: T) => void, reject: (reason: any) => void ) { if (promise === value) { return reject(new TypeError(`Chaining cycle detected for promise #<OptionalPromise>`)); } let isCall: boolean = false; if ((value && typeof value === "object") || typeof value === "function") { /* 使用 try-catch 避免如下情况 Object.defineProperty(obj, "then", { get() { throw new Error("msg xxx") } }) */ try { const thenFunctor: ((resolve: (value: T) => void, reject: (reason: any) => void) => any) | undefined | null = value["then"]; if (typeof thenFunctor === "function") { thenFunctor.call( value, (y) => { if (!isCall) { isCall = true; this.#processThenableOrNor(value, y, resolve, reject); } }, (r) => { if (!isCall) { isCall = true; reject(r); } } ); } else { resolve!(value); } } catch (reason) { if (!isCall) { isCall = true; reject(reason); } } } else { resolve!(value); } } public then<E = T, R = never>( onFulfilled?: ((value: T) => E | Thenable<E>) | undefined | null, onRejected?: ((reason: any) => R | Thenable<R>) | undefined | null ): ToyPromise<E | R> { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val as Thenable<E>; onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; const retPromise = new ToyPromise<E | R>((resolve, reject) => { switch (this.#state) { case ToyPromiseStatus.FULFILLED: queueMicrotask(() => { try { ToyPromise.#processThenableOrNor(retPromise, onFulfilled!(this.#value), resolve, reject); } catch (reason) { reject(reason); } }); break; case ToyPromiseStatus.REJECTED: queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onRejected!(this.#reason), resolve, reject); } catch (reason) { reject(reason); } }); break; case ToyPromiseStatus.PENDING: this.#resolvedCallbacks.push((value: T): void => queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onFulfilled!(value), resolve, reject); } catch (reason) { reject(reason); } }) ); this.#rejectedCallbacks.push((reason: any): void => queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onRejected!(reason), resolve, reject); } catch (reason) { reject(reason); } }) ); break; default: break; } }); return retPromise; } }
-
写的零零散散, 先简单测试一下(结果还算 OK)
实现 catch, finally 以及静态方法
-
这里省略前面已有代码...
class ToyPromise<T> implements Thenable<T> { // 前面那些代码.... public catch<TResult = never>( onRejected?: ((reason: any) => TResult | Thenable<TResult>) | undefined | null ): ToyPromise<T | TResult> { return this.then(null, onRejected) as any; } public finally(onFinally?: (() => void) | undefined | null): ToyPromise<T> { onFinally = typeof onFinally === "function" ? onFinally : () => {}; return this.then( (value) => ToyPromise.resolve(onFinally!()).then(() => value), (reason) => ToyPromise.resolve(onFinally!()).then(() => { throw reason; }) ); } static #isThenable(obj: any): boolean { if ((obj && typeof obj === "object") || typeof obj === "function") { return typeof obj["then"] === "function"; } return false; } static #isIterable(obj: any): boolean { // 避免 "" 空字符(那么是 iterable) 被判断为 false 条件 if (typeof obj === "string") { return true; } // 如果是 null 直接返回 false if (typeof obj === "object" && !obj) { return false; } // 判断是否为可迭代对象 if (typeof obj === "function" || typeof obj === "object") { /* 兼容如下这种示例情况 function foo() {} foo[Symbol.iterator] = () => { yield 233 } */ return typeof obj[Symbol.iterator] === "function"; } else { return false; } } // 这里只举例 any, 其它静态方法在完整代码示例里 /** * 处理可迭代对象的状态兑现, 只要有一个元素的状态未已解决, 那么就立即返回改元素的已解决的 `ToyPromise` 对象, * 如果所有元素的对象状态都为 `rejected`, 那么返回一个已拒绝的 `ToyPromise` 对象 * * @param values 可迭代对象 * @returns 首个状态兑现为 `fulfilled` 的 `ToyPromise` 对象, 全部为失败时返回标识失败的拒绝 `ToyPromise` 对象 */ public static any<T>(values: Iterable<T | PromiseLike<T>>): ToyPromise<ToyPromiseAwaited<T>>; /** * 处理数组内对象的状态兑现, 只要有一个元素的状态未已解决, 那么就立即返回改元素的已解决的 `ToyPromise` 对象, * 如果所有元素的对象状态都为 `rejected`, 那么返回一个已拒绝的 `ToyPromise` 对象 * * @param values 数组 * @returns 首个状态兑现为 `fulfilled` 的 `ToyPromise` 对象, 全部为失败时返回标识失败的拒绝 `ToyPromise` 对象 */ public static any<T extends readonly unknown[] | []>(values: T): ToyPromise<ToyPromiseAwaited<T[number]>>; public static any<T>(values: any) { return new ToyPromise((resolve, reject) => { if (!this.#isIterable(values)) { throw new TypeError( `${typeof values} ${values} is not iterable (cannot read property Symbol(Symbol.iterator))` ); } else { let rejectedCount: number = 0; if (Array.isArray(values)) { // 如果是空数组, 那么直接抛错 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any if (values.length === 0) { throw new AggregateError([], "All promises were rejected"); } const len0: number = values.length; for (let i: number = 0; i < len0; ++i) { const current: any = values[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then(resolve as any, () => { if (++rejectedCount === len0) { reject(new AggregateError([], "All promises were rejected")); } }); } else { resolve(current as any); } } } else { const tmpList: Array<any> = []; for (const val of values) { tmpList.push(val); } // 如果可迭代遍历累计次数为 0, 那么同样直接抛错 if (tmpList.length == 0) { throw new AggregateError([], "All promises were rejected"); } const len1: number = tmpList.length; for (let i: number = 0; i < len1; ++i) { const current: any = tmpList[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then(resolve as any, () => { if (++rejectedCount === len1) { reject(new AggregateError([], "All promises were rejected")); } }); } else { resolve(current as any); } } } } }); } }
是时候写个简单 demo 了
ToyPromise.all([ 1, 2, new Promise<number>((resolve) => setTimeout(resolve, 1000, 3)), new ToyPromise<number>((resolve) => setTimeout(resolve, 600, 4)), ToyPromise.resolve(5) ]) .then((values: Array<number>) => { console.log(values); return ToyPromise.reject("why"); }) .then() .catch() .catch((reason) => { console.warn(reason); }) .then() .finally(() => { console.log("done1"); }) .then() .finally(() => { console.log("done2"); });
浏览器输出
添加未捕获错误控制台输出提醒
-
目前为止看上去好像还可以, 但有时我们会注意到原生实现在拒绝后如果没有错误捕获, 处理, 浏览器会给一个控制台窗口 error 提示(提示一次), 如这样
-
我们这里作一些简单处理, 以此达到类似效果, 我们需要一个记录保存是否有未捕获的拒绝处理, 且确保只输出一次, 那么可以这么修改(如图 节省篇幅, 这里只截取 code 截屏)
Promise.reject("why") .then(() => { throw new Error("1"); }) .catch(() => { throw new Error("2"); }) .finally() .then(() => { return new Promise((_, reject) => { reject("3"); }); }) .catch(); ToyPromise.reject("why") .then(() => { throw new Error("1"); }) .catch(() => { throw new Error("2"); }) .finally() .then(() => { return new Promise((_, reject) => { reject("3"); }); }) .catch(); Promise.reject("nothing").finally().catch(console.warn); ToyPromise.reject("nothing").finally().catch(console.warn);
原生.all, .any ...可能的一些预期外行为,
-
这个问题是我在实现 .all 等方法时想到的, 如果传入的 values 是一个数组的代理对象, 且如果恰好 get 里有些额外行为, 会如何? 直接写一个简单示例
const baseArr: Array<number> = [1, 2, 3]; const proxyArr = new Proxy(baseArr, { get(target, p, receiver) { console.log("tricker --1"); return Reflect.get(target, p, receiver); } }); function* gen() { for (let i: number = 1; i <= 3; ++i) { console.log("tricker --2"); yield i; } } // 可能出现一些非预期内的行为, 但这里不作为探讨 Promise.all(proxyArr); Promise.all(gen());
不妨简单粗暴猜测下~~(过于直球)~~对于数组处理内部直接遍历了 12 次 + 2 次读取数组长度`(PS: z 这里存粹的主观猜测, 错了轻喷 🤣), 而对于非数组的可迭代对象, 则可能采用收集到另外一个容器的形式再次读取判断的方式(我自己写的实现实际上也是这么做) 😶🌫️
最后, 添加 abort(): boolean
以及完整的实现(又臭又长)
-
记得开头枚举定义的
ABORTED
吗, 这里添加一个abort(): boolean
用于取消一个状态为 pending 的自定义 Promise 实例(同样不可逆), 且返回一个布尔值用来判断是否唉状态落地前取消了任务 -
PS: 已存在不足: 对比原生有些地方肯定没处理好, 比如幂等, 以及 resolve 一个 Thenable 对象时的行为(其实如果 node 的 Promise 去跑那个测试会有几个测试条目过不了)..., 剩下的问题后面再改...
/** * 自定义 ToyPromise 状态 */ enum ToyPromiseStatus { PENDING = "pending", FULFILLED = "fulfilled", REJECTED = "rejected", ABORTED = "aborted" } type ToyPromiseAwaited<T> = T extends null | undefined ? T : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } ? F extends (value: infer V, ...args: infer _) => any ? ToyPromiseAwaited<V> : never : T; /** * PromiseLike 定义(摘自 ts 默认内置定义) */ interface Thenable<T> { then<TResult1 = T, TResult2 = never>( onFulfilled?: ((value: T) => TResult1 | Thenable<TResult1>) | undefined | null, onRejected?: ((reason: any) => TResult2 | Thenable<TResult2>) | undefined | null ): Thenable<TResult1 | TResult2>; } /** * 执行器函数类型 */ type ExecutorType<T> = (resolve: (value: T) => void, reject: (reason: any) => void) => void; /** * 所有自定义 Promise 状态确定所返回的值 */ type ToyPromiseAllSettledResult<T> = | { status: "fulfilled"; value: T } | { status: "rejected"; reason: any; }; /** * 自定义 Promise A+ 规范简单实现 * * @implements Thenable<T> 实现了 Thenable 接口并且继承其类型 * @template T 数据类型 * @author Shalling * @version 0.01 */ class ToyPromise<T> implements Thenable<T> { /** * 自定义 Promise 的状态 */ #state: ToyPromiseStatus; /** * 自定义 Promise 已解决时所包含的值 */ #value!: T; /** * 自定义 Promise 处于拒绝时包含的值 */ #reason: any; /** * 已解决回调函数队列 */ readonly #resolvedCallbacks: Array<(value: T) => void>; /** * 已拒绝回调函数队列 */ readonly #rejectedCallbacks: Array<(reason: any) => void>; /** * 记录处于已拒绝状态时, 是否提供了异常处理函数 */ #hasRejectedHandler: boolean; /** * 实例初始化构造器 * * @param executor 状态确定执行器函数 */ public constructor(executor: ExecutorType<T>) { if (typeof executor !== "function") { throw new TypeError(`ToyPromise resolver #<${typeof executor}> is not a function`); } this.#hasRejectedHandler = false; this.#state = ToyPromiseStatus.PENDING; this.#resolvedCallbacks = []; this.#rejectedCallbacks = []; try { executor(this.#resolve.bind(this), this.#reject.bind(this)); } catch (reason: any) { this.#reject(reason); } } /** * 提供给状态执行器函数的 `已解决` 函数 * * @param value `已解决` 值 * @template T 默认数据类型 */ #resolve(value: T): void { if (<ToyPromise<T>>value === this) { this.#reject(new TypeError("Chaining cycle detected for promise #<OptionalPromise>")); return; } if (value instanceof Promise || value instanceof ToyPromise) { (<Thenable<T>>value).then(this.#resolve.bind(this), this.#reject.bind(this)); return; } // TODO: WAIT RESOLVE // if (ToyPromise.#isThenable(value)) { // ToyPromise.#processThenableOrNor(this, value, this.#resolve.bind(this), this.#reject.bind(this)); // return; // } if (this.#state === ToyPromiseStatus.PENDING) { this.#value = value; this.#state = ToyPromiseStatus.FULFILLED; for (const cb of this.#resolvedCallbacks) { cb(this.#value); } } } /** * 提供给状态执行器函数的 `已拒绝` 函数 * * @param reason `已拒绝` 原因 * @private */ #reject(reason: any): void { if (this.#state === ToyPromiseStatus.PENDING) { this.#reason = reason; this.#state = ToyPromiseStatus.REJECTED; for (const cb of this.#rejectedCallbacks) { cb(this.#reason); } queueMicrotask(() => { if (!this.#hasRejectedHandler) { console.error(`Uncaught (in toy-promise)📌`, reason); } }); } } /** * 通过递归方式处理可能为 `Thenable` 结构的对象 * * @param promise 自定义 Promise 实例 * @param value 进行判断处理的值 * @param resolve 已解决函数 * @param reject 已拒绝函数 * @template T 默认数据类型 */ static #processThenableOrNor<T>( promise: ToyPromise<T>, value: any, resolve: (value: T) => void, reject: (reason: any) => void ) { if (promise === value) { return reject(new TypeError(`Chaining cycle detected for promise #<OptionalPromise>`)); } let isCall: boolean = false; if ((value && typeof value === "object") || typeof value === "function") { /* 使用 try-catch 避免如下情况 Object.defineProperty(obj, "then", { get() { throw new Error("msg xxx") } }) */ try { const thenFunctor: ((resolve: (value: T) => void, reject: (reason: any) => void) => any) | undefined | null = value["then"]; if (typeof thenFunctor === "function") { thenFunctor.call( value, (y) => { if (!isCall) { isCall = true; this.#processThenableOrNor(value, y, resolve, reject); } }, (r) => { if (!isCall) { isCall = true; reject(r); } } ); } else { resolve!(value); } } catch (reason) { if (!isCall) { isCall = true; reject(reason); } } } else { resolve!(value); } } /** * 返回链式生成的自定义 promise 实例 * * @param onFulfilled `已解决` 状态捕获函数 * @param onRejected `已拒绝/错误` 状态捕获调函数 * @template T 接收的已解决类型 * @template R 接收的以决绝类型 * @return ToyPromise 一个新的自定义 promise 实例 */ public then<E = T, R = never>( onFulfilled?: ((value: T) => E | Thenable<E>) | undefined | null, onRejected?: ((reason: any) => R | Thenable<R>) | undefined | null ): ToyPromise<E | R> { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val as Thenable<E>; onRejected = typeof onRejected === "function" ? onRejected : (reason) => { throw reason; }; const retPromise = new ToyPromise<E | R>((resolve, reject) => { switch (this.#state) { case ToyPromiseStatus.FULFILLED: queueMicrotask(() => { try { ToyPromise.#processThenableOrNor(retPromise, onFulfilled!(this.#value), resolve, reject); } catch (reason) { reject(reason); } }); break; case ToyPromiseStatus.REJECTED: queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onRejected!(this.#reason), resolve, reject); } catch (reason) { reject(reason); } }); break; /* 如果是 pending 状态(如 executor 在 setTimeout 中执行), 则将包装函数推入队列, 等 executor 到达执行状态, 内部的 #resolve(val), #reject(reason) 各自遍历 `pending` 状态时推入队列里的包装函数 */ case ToyPromiseStatus.PENDING: this.#resolvedCallbacks.push((value: T): void => queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onFulfilled!(value), resolve, reject); } catch (reason) { reject(reason); } }) ); this.#rejectedCallbacks.push((reason: any): void => queueMicrotask((): void => { try { ToyPromise.#processThenableOrNor(retPromise, onRejected!(reason), resolve, reject); } catch (reason) { reject(reason); } }) ); break; default: break; } }); this.#hasRejectedHandler = true; return retPromise; } /** * `错误/已拒绝` 状态处理方法 * * @param onRejected `已拒绝/错误` 状态捕获调函数 * @define 这个方法为 `then` 方法的签名 * @return ToyPromise 一个新的自定义 promise 实例 */ public catch<TResult = never>( onRejected?: ((reason: any) => TResult | Thenable<TResult>) | undefined | null ): ToyPromise<T | TResult> { return this.then(null, onRejected) as any; } /** * 最终执行函数(这个方为 `then` 方法的签名版本) * * @param onFinally 回调执行函数 * @return 一个新的自定义 promise 实例(会保持之前的 `已解决/已拒绝` 状态 */ public finally(onFinally?: (() => void) | undefined | null): ToyPromise<T> { // 提前处理 ToyPromise.ts:152 Uncaught (in toy-promise)📌 TypeError: onFinally is not a function 这种可能的情况 onFinally = typeof onFinally === "function" ? onFinally : () => {}; return this.then( (value) => ToyPromise.resolve(onFinally!()).then(() => value), (reason) => ToyPromise.resolve(onFinally!()).then(() => { throw reason; }) ); } /** * 判断一个对象是否为 `thenable` 对象 * * @param obj 需要进行判断的对象 * @return 对象是否为 thenable 对象 */ static #isThenable(obj: any): boolean { if ((obj && typeof obj === "object") || typeof obj === "function") { return typeof obj["then"] === "function"; } return false; } /** * 判断一个对象是否为可迭代对象 * * @param obj 需要进行判断的对象 * @return 对象是否为可迭代对象 */ static #isIterable(obj: any): boolean { // 避免 "" 空字符(那么是 iterable) 被判断为 false 条件 if (typeof obj === "string") { return true; } // 如果是 null 直接返回 false if (typeof obj === "object" && !obj) { return false; } // 判断是否为可迭代对象 if (typeof obj === "function" || typeof obj === "object") { /* 兼容如下这种示例情况 function foo() {} foo[Symbol.iterator] = () => { yield 233 } */ return typeof obj[Symbol.iterator] === "function"; } else { return false; } } /** * 根据是否在状态是否已兑现前打断操作返回处理的结果 * 如果当前状态为未兑现(即为: pending), 那么将其操作提前放弃 * 否则如果状态已经处于确定状态: `fulfilled/rejected`, 那么不做任何修改 * * @returns 如果在状态未兑现前结束操作返回 {@code true}, 否则返回 {@code false} */ public abort(): boolean { if (this.#state === ToyPromiseStatus.PENDING) { this.#state = ToyPromiseStatus.ABORTED; this.#reason = "aborted"; return true; } else { return false; } } /** * @param values 可迭代对象 * @returns 如果可迭代对象内的所有元素的兑现状态为已解决, 那么返回包含可迭代对象遍历的所有元所组成的已解决 `ToyPromise` 对象, 否则返回包含已拒绝的 `ToyPromise` 对象 */ public static all<T>(values: Iterable<T | PromiseLike<T>>): ToyPromise<ToyPromiseAwaited<T>[]>; /** * @param values 数组 * @returns 如果数组内的所有元素的兑现状态为已解决, 那么返回包含可迭代对象遍历的所有元所组成的已解决 `ToyPromise` 对象, 否则返回包含已拒绝的 `ToyPromise` 对象 */ public static all<T extends readonly unknown[] | []>( values: T ): ToyPromise<{ -readonly [P in keyof T]: ToyPromiseAwaited<T[P]> }>; public static all<T>(values: any) { return new ToyPromise<any>((resolve, reject) => { // 如果接收值不可迭代, 直接返回一个包含错误提示的 ToyPromise if (!this.#isIterable(values)) { throw new TypeError( `${typeof values} ${values} is not iterable (cannot read property Symbol(Symbol.iterator))` ); } else { // 已解决统计 let resolvedCount: number = 0; // 存放已解决值的数组 const resolvedList: Array<any> = []; const processStatus = (idx: number, data: any, countLen: number) => { resolvedList[idx] = data; if (++resolvedCount === countLen) { resolve(resolvedList as any); } }; // 如果是正常 Array, 直接遍历 if (Array.isArray(values)) { const len0: number = values.length; loop: for (let i: number = 0; i < len0; ++i) { const current: any = values[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then((value) => { processStatus(i, value, len0); }, reject); } else { processStatus(i, current, len0); } } } else { const tmpList: Array<any> = []; for (const val of values) { tmpList.push(val); } const len1: number = tmpList.length; for (let i: number = 0; i < len1; ++i) { const current = tmpList[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then((value) => { processStatus(i, value, len1); }, reject); } else { processStatus(i, current, len1); } } } } }); } /** * 等待所有可迭代对象内所有元素状态已兑现(fulfilled 或者 rejected), 返回处理结果 * * @param values 可迭代对象 * @returns 可跌打兑现内所有元素兑现状态所组成的数组(会维持可迭代兑现元素遍历顺序) */ public static allSettled<T>( values: Iterable<T | Thenable<T>> ): ToyPromise<ToyPromiseAllSettledResult<ToyPromiseAwaited<T>>[]>; /** * 等待所有可迭代对象状态已兑现(fulfilled 或者 rejected), 返回处理结果 * * @param values 一个数组对象(元素可以包含 Thenable 对象) * @returns 所有元素兑现状态所组成的数组(会维持原有数组的元素位置) */ public static allSettled<T extends readonly unknown[] | []>( values: T ): ToyPromise<{ -readonly [P in keyof T]: ToyPromiseAllSettledResult<ToyPromiseAwaited<T[P]>>; }>; public static allSettled<T>(values: any) { return new ToyPromise<any>((resolve) => { if (!this.#isIterable(values)) { if (!this.#isIterable(values)) { throw new TypeError( `${typeof values} ${values} is not iterable (cannot read property Symbol(Symbol.iterator))` ); } } else { let doneCount: number = 0; const doneList: Array<any> = []; const processStatus = ( index: number, data: any, tType: ToyPromiseStatus.FULFILLED | ToyPromiseStatus.REJECTED = ToyPromiseStatus.FULFILLED, length: number ): void => { doneList[index] = { status: tType === ToyPromiseStatus.FULFILLED ? tType : ToyPromiseStatus.REJECTED, [tType === ToyPromiseStatus.FULFILLED ? "value" : "reason"]: data }; if (++doneCount === length) { resolve(doneList as any); } }; // 如果为数组那么直接遍历 if (Array.isArray(values)) { const listLen: number = values.length; for (let i: number = 0; i < listLen; ++i) { const current: any = values[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then( (value) => { processStatus(i, value, ToyPromiseStatus.FULFILLED, listLen); }, (reason) => { processStatus(i, reason, ToyPromiseStatus.REJECTED, listLen); } ); } else { processStatus(i, current, ToyPromiseStatus.FULFILLED, listLen); } } } // 处理非数组但可迭代的数据 else { const tmpList: Array<any> = []; for (const val of values) { tmpList.push(val); } const tmpListLen: number = tmpList.length; for (let i: number = 0; i < tmpListLen; ++i) { const current = tmpList[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then( (value) => { processStatus(i, value, ToyPromiseStatus.FULFILLED, tmpListLen); }, (reason) => { processStatus(i, reason, ToyPromiseStatus.REJECTED, tmpListLen); } ); } else { processStatus(i, current, ToyPromiseStatus.FULFILLED, tmpListLen); } } } } }); } /** * 处理可迭代对象的状态兑现, 只要有一个元素的状态未已解决, 那么就立即返回改元素的已解决的 `ToyPromise` 对象, * 如果所有元素的对象状态都为 `rejected`, 那么返回一个已拒绝的 `ToyPromise` 对象 * * @param values 可迭代对象 * @returns 首个状态兑现为 `fulfilled` 的 `ToyPromise` 对象, 全部为失败时返回标识失败的拒绝 `ToyPromise` 对象 */ public static any<T>(values: Iterable<T | PromiseLike<T>>): ToyPromise<ToyPromiseAwaited<T>>; /** * 处理数组内对象的状态兑现, 只要有一个元素的状态未已解决, 那么就立即返回改元素的已解决的 `ToyPromise` 对象, * 如果所有元素的对象状态都为 `rejected`, 那么返回一个已拒绝的 `ToyPromise` 对象 * * @param values 数组 * @returns 首个状态兑现为 `fulfilled` 的 `ToyPromise` 对象, 全部为失败时返回标识失败的拒绝 `ToyPromise` 对象 */ public static any<T extends readonly unknown[] | []>(values: T): ToyPromise<ToyPromiseAwaited<T[number]>>; public static any<T>(values: any) { return new ToyPromise((resolve, reject) => { if (!this.#isIterable(values)) { throw new TypeError( `${typeof values} ${values} is not iterable (cannot read property Symbol(Symbol.iterator))` ); } else { let rejectedCount: number = 0; if (Array.isArray(values)) { // 如果是空数组, 那么直接抛错 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/any if (values.length === 0) { throw new AggregateError([], "All promises were rejected"); } const len0: number = values.length; for (let i: number = 0; i < len0; ++i) { const current: any = values[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then(resolve as any, () => { if (++rejectedCount === len0) { reject(new AggregateError([], "All promises were rejected")); } }); } else { resolve(current as any); } } } else { const tmpList: Array<any> = []; for (const val of values) { tmpList.push(val); } // 如果可迭代遍历累计次数为 0, 那么同样直接抛错 if (tmpList.length == 0) { throw new AggregateError([], "All promises were rejected"); } const len1: number = tmpList.length; for (let i: number = 0; i < len1; ++i) { const current: any = tmpList[i]; if (this.#isThenable(current)) { (<Thenable<T>>current).then(resolve as any, () => { if (++rejectedCount === len1) { reject(new AggregateError([], "All promises were rejected")); } }); } else { resolve(current as any); } } } } }); } /** * 处理可迭代对象内第一个元素状态兑现的结果, 根据落地状态立即返回其结果 * * @param values 可迭代对象 * @returns 首个状态已兑现 的 `ToyPromise` 对象 */ public static race<T>(values: Iterable<T | Thenable<T>>): ToyPromise<ToyPromiseAwaited<T>>; /** * 处理可迭代对象内第一个元素状态兑现的结果, 根据落地状态立即返回其结果 * * @param values 数组对象 * @returns 首个状态已兑现 的 `ToyPromise` 对象 */ public static race<T extends readonly unknown[] | []>(values: T): ToyPromise<ToyPromiseAwaited<T[number]>>; public static race<T>(values: any) { return new ToyPromise((resolve, reject) => { for (const val of values) { if (this.#isThenable(val)) { (<Thenable<T>>val).then(resolve as any, reject); } else { resolve(val as any); } } }); } /** * @returns 返回一个状态为已解决的 ToyPromise<void> 对象 */ public static resolve(): ToyPromise<void>; /** * 如果传入对象为一个 期约对象, 那么返回的对象为幂等, 即: a: Thenable; c = static.resolve(a), a === c * * @param value 期待值 * @returns 返回一个状态为已解决的 ToyPromise<T> 的对象, 即使传入的对象为一个未状态拒绝的 Thenable 对象 */ public static resolve<T>(value: T): ToyPromise<ToyPromiseAwaited<T>>; /** * 如果传入对象为一个 期约对象, 那么返回的对象为幂等, 即: a: Thenable; c = static.resolve(a), a === c * * @param value 期待值 * @returns 返回一个状态为已解决的 ToyPromise<T> 的对象, 即使传入的对象为一个未状态拒绝的 Thenable 对象 */ public static resolve<T>(value: T | Thenable<T>): ToyPromise<ToyPromiseAwaited<T>>; public static resolve(value?: any) { // 接口幂等, 兼容原生 Promise 和自定义 Promise /* const p1 = new ToyPromise(()=> {}); const p2 = ToyPromise.resolve(p1); console.log(p1 === p2); // true */ /* const p1 = new ToyPromise((_ignored, reject) => { reject(111); }); const p2 = ToyPromise.resolve(p1); console.log(p1 === p2); // true */ if (value instanceof ToyPromise || value instanceof Promise) { return value; } return new ToyPromise((resolve) => { resolve(value); }); } /** * @param reason 决绝原因/值 * @returns 返回一个状态为已拒绝的 ToyPromise<T> 的对象 */ public static reject<T = never>(reason?: any): ToyPromise<T> { return new ToyPromise<T>((_ignore, reject) => { reject(reason); }); } /** * 返回一个对象, 其包含一个新的 ToyPromise 对象和两个函数, 用于解决或拒绝它, 对应于传入给 ToyPromise() 构造函数执行器的两个参数 * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers * * @returns 包含一个新的 ToyPromise 实例对象和两个函数, 用于解决或拒绝它 */ public static withResolvers<T = unknown>(): { promise: ToyPromise<unknown>; resolve: (val: T) => void; reject: (reason: any) => void; } { let res: (val: T) => void; let rej: (reason: any) => void; const promise = new ToyPromise((resolve, reject) => { res = resolve; rej = reject; }); return { promise, resolve: res!, reject: rej! }; } public get [Symbol.toStringTag](): string { return "ToyPromise"; } } export { ToyPromise };
-
以及一个简单的测试
const tp = new ToyPromise<number>((resolve) => { setTimeout(resolve, 1000, 4); }) .then((value) => { console.log(value); return ToyPromise.all([...Array.from(gen(3)), value]); }) .then((values: Array<number>) => { console.log(values); return values.reduce((prev, current) => prev + current, 0); }) .then(console.log, () => { throw ""; }) .then(() => { throw "hint"; }) .catch(console.warn) .finally(() => { console.log("apple seed"); }); setTimeout(() => { const isPrevBrokeSuccess = tp.abort(); isPrevBrokeSuccess ? console.log(" ---> 提前终止") : console.warn("状态已定"); }, 1000); tp.then(() => { return new ToyPromise((resolve) => setTimeout(resolve, 1800, "A")); }).finally(() => { console.log("never got it!"); });