Promise A+规范原文翻译

81 阅读12分钟

promiseA+规范

一个开放、健全且通用的JavaScript Promise标准 --- 由实现者指定,为实现者服务

一个promise代表了某个异步操作的最终结果。与promise交互的主要方式是通过其 then 方法,该方法注册了两个回调函数,一个用于接收promise的最终值(fulfillment value),另一个则是用于接收promise不能被履行的原因(rejection reason)。

本规范详细规定了 then 方法的行为,为所有符合PromiseA+规范的实施提供了一个可互操作的基础。因此,本规范应被视为非常稳定。尽管PromiseA+组织可能会为了处理新发现的极端情况而偶尔修订本规范,但只会经过仔细权衡、讨论和测试后,才会进行不兼容的较大更改或向下兼容的修订。

从历史上看,PromiseA+澄清了早期PromiseA提案中的行为条款,将其在事实上的扩展约定明确化,并省略了那些未经验证或有问题的部分。

最后,PromiseA+规范的核心内容没有涉及如何创建(create)、履行(fulfill)或拒绝(reject)promise,而是选择专注于一个可互操作的 then 方法。未来在配套规范中可能涉及这些主题。


1.术语

1.1 promise是一个拥有 then 方法的对象或函数,其行为符合本规范。

1.2 thenable是一个定义了 then 方法的对象或函数。

1.3 值(value)是任何合法的JavaScript值(包括undefined/thenable/promise)。

1.4 异常(exception)是使用 throw 语句抛出的值。

1.5 拒绝原因(reason)是一个值,用于说明promise被拒绝的原因。


2. 要求

2.1 Promise状态

一个promise必须处于以下三种状态之一:等待中(pending)、已履行(fulfilled)或已拒绝(rejected)。

2.1.1 当处于等待中(pending)状态时,promise:

  2.1.1.1 可以转换到已履行(fulfilled)或者已拒绝(rejected)状态。

2.1.2 当处于已履行(fulfilled)状态时,promise:

  2.1.2.1 不能转换任何其他状态。

  2.1.2.2 必须是一个,且该值不能改变。

2.1.3 当处于已拒绝(rejected)状态时,promise:

  2.1.3.1 不能转换任何其他状态。

  2.1.3.2 必须有一个拒绝原因,且原因不能更改。

这里的“不能更改”指的是不可变身份(即===),并不意味着深度不可变。

2.2 then方法

promise必须提供一个then方法来访问当前或最终拒绝原因

一个promise的 then 方法接收两个参数:

promise.then(onFulfilled,onRejected)

2.2.1 onFulfilledonRejected 都是可选参数:

  2.2.1.1 如果 onFulfilled 不是函数,它必须被忽略

  2.2.1.2 如果 onRejected 不是函数,它必须被忽略

2.2.2 如果 onFulfilled 是一个函数:

  2.2.2.1 它必须在 promise 进入已履行状态后被调用,并将 promise作为其第一个参数。

  2.2.2.2 它在 promise 进入已履行状态之前不能被调用。 2.2.2.3 它最多只能被调用一次。

2.2.3 如果 onRejected 是一个函数:

  2.2.3.1 它必须在 promise 进入 已拒绝 状态后被调用,并将 promise拒绝原因 作为其第一个参数。

  2.2.3.2 它在 promise 进入已拒绝状态之前不能被调用。

  2.2.3.3 它最多只能被调用一次。

2.2.4 onFulfilledonRejected 必须必须等到执行上下文栈中仅包含平台代码时才能背调用。[3.1]

2.2.5 onFulfilledonRejected 必须作为函数调用(即没有 this 值)。 [3.2]

2.2.6 同一个 then 方法可以在用一个promise上被多次调用。

  2.2.6.1 当 promise 进入 已履行状态时,所有相应的 onFulfilled 回调必须按照它们原始调用 then 的顺序执行。

  2.2.6.2 当 promise 进入 已拒绝状态时,所有相应的 onRejected 回调必须按照它们原始调用 then 的顺序执行。

2.2.7 then 方法必须返回一个promise。[3.3]

promise2 = promise1.then(onFulfilled,onRejected)

  2.2.7.1 如果 onFulfilledonRejected 返回一个值 x ,则执行promise解决过程 [[Resolve]](promise2,x)

  2.2.7.2 如果 onFulfilledonRejected 抛出一个异常e ,则promise2 必须以e作为拒绝原因被拒绝。

  2.2.7.3 如果 onFulfilled 不是一个函数,并且promise1 进入已履行状态,那么promise2 必须以与promise1 相同的进入已履行状态。

  2.2.7.4 如果onRejected 不是一个函数,并且promise1 进入已拒绝状态,那么promise2 必须以与promise1 相同的 拒绝原因进入 已拒绝状态。

2.3 Promise解决过程

Promise解决过程 是一个抽象操作,它以一个promise和一个值作为输入,我们将其表示为 [[Resolve]](promise,x) 。如果 x 是一个thenable,它会尝试让 promise 采用 x 的状态(前提是x 的行为至少像一个promise)。否则,它将用值 x 来履行 promise

这种对thenable的处理使得promise可以实现互操作,只要它们公开一个符合PromiseA+规范的then 方法。它还允许PromiseA+实现通过合理的then 方法来”同化“(assimilate)不符合规范的实现。

要运行 [[Resolve]](promise,x) ,请执行一下步骤:

2.3.1 如果 promisex 指向同一个对象,则以一个 TypeError 作为 拒绝原因来拒绝 promise

2.3.2 如果 x 是一个promise,则采用其状态:[3.4]

  2.3.2.1 如果 x 处于等待中(pending)状态, promise 必须保持等待中状态,直到 x 被履行或被拒绝。

  2.3.2.2 如果 x 处于已履行(fulfilled)状态,用相同的值履行 promise

  2.3.2.3 如果 x 处于已拒绝(rejected)状态,用相同的错误原因拒绝 promise

2.3.3 否则,如果 x 是一个对象或函数。则:

  2.3.3.1 令 thenx.then 。[3.5]

  2.3.3.2 如果获取 x.then 属性的结果抛出一个异常 e ,则以 e 作为拒绝原因拒绝 promise

  2.3.3.3 如果 then 是一个函数,则将 x 作为 this 来调用它,第一个参数是 resolvePromise ,第二个参数是 rejectPromise ,其中:

    2.3.3.3.1 如果 resolvePromise 被一个 y 调用,则执行 [[Resolve]](promise,y)

    2.3.3.3.2 如果 resolvePromise 被一个拒绝原因 r 调用,则以 r 拒绝 promise

    2.3.3.3.3 如果 resolvePromiserejectPromise 都被调用,或者对同一个参数进行多次调用,则首次调用优先,任何后续的调用都将被忽略。

    2.3.3.3.4 如果调用 then 方法抛出了异常 e

      2.3.3.3.4.1 如果 resolvePromiserejectPromise 已经被调用过,则忽略该异常。

      2.3.3.3.4.2 否则,以 e 作为拒绝原因拒绝 promise

  2.3.3.4 如果 then 不是一个函数,则以 x 作为值履行 promise

2.3.4 如果 x 不是对象或函数,则以 x 作为值履行 promise

如果一个promise被一个thenable解决,而这个thenable参与了一个循环的thenable链,这样 [[Resolve]](promise,thenable) 的递归性质最终会导致 [[Resolve]](promise,thenable) 再次被调用,按照上述算法,这将导致无线递归。我们鼓励但是不强制要求实现检测这种递归并以一个信息丰富的 TypeError 作为拒绝原因来拒绝 promise 。[3.6]


3. 注释

  1. 这里的“平台代码”指的是引擎、环境以及Promise实现代码。在实践中,这个要求确保了 onFulfilledonRejected 能够异步的、在新的一轮事件循环中(after the event loop turn in which then is called)执行,并且调用栈是最新的。这可以通过“macro-task” 机制(如setTimeout或setImmediate)或“micro-task”(如MutationObserver 或 process.nextTick)来实现。由于promise实现本身被认为是“平台代码”,因此处理程序被调用时,它本身可能已经包含一个任务调度队列或“trampoline”。
  2. 在严格模式下(strict mode), this 的值是 undefined ;在非严格模式下(sloppy mode),this的值是全局对象。
  3. 如果实现满足所有要求,则允许 promise2 === promise1 。每个实现都应该记录它是否能产生 promise2===promise1 以及在什么条件下会产生。
  4. 通常,只有当 x 来自当前实现时,我们才知道它是一个真正的promise。这条条款允许使用特定实现的方法来采用一直符合规范的promise的状态。
  5. 此过程首先存储对 x.then 的引用,然后测试该引用,然后在调用该引用,以避免多次访问 x.then 属性。这种预防措施对于确保在属性访问过程中行为的一致性非常重要,因为属性的值在多次检索之间可能会发生变化。
  6. 实现不应该在thenable链的额深度上做任何限制,并且假设超出任意限制的递归是无限的。只有真正的循环才会引起 TypeError ;如果遇到一个无线长的不同thenable链,那么无线递归下去时正确的行为。

官网原文参见

完整Promise手写源码(带上所有ES6+静态方法)

/**
 * 符合 Promise A+ 规范的 Promise 实现
 * 参考:https://promisesaplus.com/
 */

// Promise 的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

/**
 * Promise A+ 2.3: The Promise Resolution Procedure
 * 处理 Promise 解析过程
 */
function resolvePromise(promise, x, resolve, reject) {
    // 2.3.1: 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    if (promise === x) {
        return reject(new TypeError('Chaining cycle detected for promise'));
    }

    // 2.3.2: 如果 x 为 Promise,则使 promise 接受 x 的状态
    if (x instanceof MyPromise) {
        // 2.3.2.1: 如果 x 处于等待态,promise 需保持为等待态直至 x 被拒绝或解决
        // 2.3.2.2: 如果 x 处于执行态,用相同的值执行 promise
        // 2.3.2.3: 如果 x 处于拒绝态,用相同的据因拒绝 promise
        x.then(
            y => resolvePromise(promise, y, resolve, reject),
            reject
        );
        return;
    }

    // 2.3.3: 如果 x 为对象或者函数
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then;
        try {
            // 2.3.3.1: 把 x.then 赋值给 then
            then = x.then;
        } catch (e) {
            // 2.3.3.2: 如果取 x.then 的值时抛出错误 e,则以 e 为据因拒绝 promise
            return reject(e);
        }

        // 2.3.3.3: 如果 then 是函数
        if (typeof then === 'function') {
            let called = false;
            try {
                // 2.3.3.3.1: 调用 then 方法,x 作为 this,两个回调函数作为参数
                then.call(
                    x,
                    // 2.3.3.3.2: 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                    y => {
                        // 2.3.3.3.3: 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
                        if (called) return;
                        called = true;
                        resolvePromise(promise, y, resolve, reject);
                    },
                    // 2.3.3.3.4: 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    r => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                );
            } catch (e) {
                // 2.3.3.3.4: 如果调用 then 方法抛出了异常 e
                // 2.3.3.3.4.1: 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
                if (called) return;
                // 2.3.3.3.4.2: 否则以 e 为据因拒绝 promise
                reject(e);
            }
        } else {
            // 2.3.3.4: 如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    } else {
        // 2.3.4: 如果 x 不为对象或者函数,以 x 为参数执行 promise
        resolve(x);
    }
}

class MyPromise {
    /**
     * Promise A+ 1: Terminology
     * 构造函数,接收一个执行器函数
     */
    constructor(executor) {
        this.state = PENDING; // Promise 当前状态
        this.value = undefined; // Promise 的终值
        this.reason = undefined; // Promise 的据因
        this.onFulfilledCallbacks = []; // Promise 成功时的回调队列
        this.onRejectedCallbacks = []; // Promise 失败时的回调队列

        /**
         * Promise A+ 1.3: "fulfilled" 状态转换函数
         */
        const resolve = value => {
            // 2.1.1: 当状态为 pending 时才能转换为 fulfilled
            if (this.state === PENDING) {
                this.state = FULFILLED;
                this.value = value;
                // 2.2.6.1: 当 Promise 成功执行时,所有对应的 onFulfilled 回调必须按照它们对应的 then 的调用顺序来执行
                this.onFulfilledCallbacks.forEach(callback => callback());
            }
        };

        /**
         * Promise A+ 1.5: "rejected" 状态转换函数
         */
        const reject = reason => {
            // 2.1.1: 当状态为 pending 时才能转换为 rejected
            if (this.state === PENDING) {
                this.state = REJECTED;
                this.reason = reason;
                // 2.2.6.2: 当 Promise 被拒绝执行时,所有对应的 onRejected 回调必须按照它们对应的 then 的调用顺序来执行
                this.onRejectedCallbacks.forEach(callback => callback());
            }
        };

        try {
            // 立即执行 executor
            executor(resolve, reject);
        } catch (error) {
            // 如果执行器抛出异常,Promise 应该被拒绝
            reject(error);
        }
    }

    /**
     * Promise A+ 2.2: The then Method
     * then 方法必须返回一个 Promise
     */
    then(onFulfilled, onRejected) {
        // 2.2.1: onFulfilled 和 onRejected 都是可选参数
        // 2.2.7.3: 如果 onFulfilled 不是函数,它必须被忽略
        // 2.2.7.4: 如果 onRejected 不是函数,它必须被忽略
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        // 2.2.7: then 必须返回一个 Promise
        const promise2 = new MyPromise((resolve, reject) => {
            if (this.state === FULFILLED) {
                // 2.2.4: onFulfilled 或 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用
                setTimeout(() => {
                    try {
                        // 2.2.2.1: onFulfilled 必须在 promise  fulfilled 后执行,并以 promise 的 value 作为第一个参数
                        const x = onFulfilled(this.value);
                        // 2.2.7.1: 如果 onFulfilled 或 onRejected 返回一个值 x,则运行 Promise 解决过程 [[Resolve]](promise2, x)
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        // 2.2.7.2: 如果 onFulfilled 或 onRejected 抛出异常 e,则 promise2 必须拒绝执行,并返回拒因 e
                        reject(error);
                    }
                }, 0);
            } else if (this.state === REJECTED) {
                setTimeout(() => {
                    try {
                        // 2.2.3.1: onRejected 必须在 promise  rejected 后执行,并以 promise 的 reason 作为第一个参数
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === PENDING) {
                // 2.2.6: then 可能会被同一个 promise 调用多次
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });

        return promise2;
    }

    /**
     * ES6 Promise.prototype.catch
     * 相当于 then(null, onRejected)
     */
    catch(onRejected) {
        return this.then(null, onRejected);
    }

    /**
     * ES6 Promise.prototype.finally
     * 无论 Promise 成功还是失败都会执行的回调
     */
    finally(callback) {
        return this.then(
            value => MyPromise.resolve(callback()).then(() => value),
            reason => MyPromise.resolve(callback()).then(() => { throw reason; })
        );
    }

    /**
     * ES6 Promise.resolve
     * 创建一个已解决的 Promise
     */
    static resolve(value) {
        // 如果 value 已经是 Promise 实例,直接返回
        if (value instanceof MyPromise) {
            return value;
        }

        // 如果 value 是 thenable 对象
        if (value && typeof value === 'object' && typeof value.then === 'function') {
            return new MyPromise(value.then);
        }

        // 其他情况返回一个以 value 为终值的 Promise
        return new MyPromise(resolve => resolve(value));
    }

    /**
     * ES6 Promise.reject
     * 创建一个已拒绝的 Promise
     */
    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    /**
     * ES6 Promise.all
     * 等待所有 Promise 完成,或第一个 Promise 被拒绝
     */
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            if (!Array.isArray(promises)) {
                return reject(new TypeError('Argument must be an array'));
            }

            if (promises.length === 0) {
                return resolve([]);
            }

            const results = new Array(promises.length);
            let completedCount = 0;

            promises.forEach((promise, index) => {
                MyPromise.resolve(promise).then(
                    value => {
                        results[index] = value;
                        completedCount++;

                        if (completedCount === promises.length) {
                            resolve(results);
                        }
                    },
                    reject // 任何一个 Promise 被拒绝,整个 all Promise 就被拒绝
                );
            });
        });
    }

    /**
     * ES6 Promise.race
     * 返回第一个敲定的 Promise(无论是完成还是拒绝)
     */
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            if (!Array.isArray(promises)) {
                return reject(new TypeError('Argument must be an array'));
            }

            if (promises.length === 0) {
                // 空数组的 race 会永远保持 pending
                return;
            }

            promises.forEach(promise => {
                MyPromise.resolve(promise).then(resolve, reject);
            });
        });
    }

    /**
     * ES2020 Promise.allSettled
     * 等待所有 Promise 敲定(无论是完成还是拒绝)
     */
    static allSettled(promises) {
        return new MyPromise(resolve => {
            if (!Array.isArray(promises)) {
                return reject(new TypeError('Argument must be an array'));
            }

            if (promises.length === 0) {
                return resolve([]);
            }

            const results = new Array(promises.length);
            let completedCount = 0;

            const checkCompletion = () => {
                if (++completedCount === promises.length) {
                    resolve(results);
                }
            };

            promises.forEach((promise, index) => {
                MyPromise.resolve(promise).then(
                    value => {
                        results[index] = { status: FULFILLED, value };
                        checkCompletion();
                    },
                    reason => {
                        results[index] = { status: REJECTED, reason };
                        checkCompletion();
                    }
                );
            });
        });
    }

    /**
     * ES2021 Promise.any
     * 等待第一个完成的 Promise,如果所有 Promise 都被拒绝,则聚合所有拒绝原因
     */
    static any(promises) {
        return new MyPromise((resolve, reject) => {
            if (!Array.isArray(promises)) {
                return reject(new TypeError('Argument must be an array'));
            }

            if (promises.length === 0) {
                return reject(new AggregateError([], 'All promises were rejected'));
            }

            const errors = new Array(promises.length);
            let rejectedCount = 0;

            promises.forEach((promise, index) => {
                MyPromise.resolve(promise).then(
                    resolve, // 任何一个 Promise 完成,整个 any Promise 就完成
                    reason => {
                        errors[index] = reason;
                        rejectedCount++;

                        if (rejectedCount === promises.length) {
                            reject(new AggregateError(errors, 'All promises were rejected'));
                        }
                    }
                );
            });
        });
    }
}

// 测试用例
if (typeof module !== 'undefined' && module.exports) {
    module.exports = MyPromise;
}

// 使用示例
if (typeof window !== 'undefined') {
    window.MyPromise = MyPromise;
}

MyPromise.deferred = function () {
    var result = {};
    result.promise = new MyPromise(function (resolve, reject) {
        result.resolve = resolve;
        result.reject = reject;
    });

    return result;
}

注意:如果想测试自己手写的MyPromise是否符合PromiseA+标准,请进行如下操作

1. 下载测试依赖
    pnpm install promises-aplus-tests
2.MyPromise最下面补充deferred代码
    MyPromise.deferred = function () {
    var result = {};
    result.promise = new MyPromise(function (resolve, reject) {
        result.resolve = resolve;
        result.reject = reject;
    });

    return result;
}
3. 配置脚本 
     "scripts": {
     "test": "promises-aplus-tests MyPromise"
          }
4. pnpm run test 
5. 如果跑完全部通过 恭喜你实现了一个通过PromiseA+规范的MyPromise!!!