# 👉 Promise/A+ 实现原理

Promise 是前端异步编程解决方案之一。

Promises/A+ 规范中表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符。

# Promise/A+ 规范-实现

// 工具函数
const isObject = (obj) =>
    Object.prototype.toString.call(obj) === "[object Object]";
const isFunction = (func) => typeof func === "function";
const isPromise = (func) => func instanceof _Promise;

// in 用于判断【指定的属性】是否在指定的对象或其原型链中
const isThenable = (obj) =>
    (isObject(obj) || isFunction(obj)) && "then" in obj && isFunction(obj.then);

// 2.1.1 有三种状态
const PENDING = Symbol("pending");
const FULFILLED = Symbol("fulfilled");
const REJECTED = Symbol("rejected");

function _Promise(executor) {
    // 确保handler是一个函数
    if (!isFunction(executor)) {
        throw new Error("Promise must accept a function as a parameter");
    }

    const self = this;

    // 状态
    self.state = PENDING;

    // 值
    self.result = undefined; //成功结果
    self.reason = undefined; //错误原因

    // 收集回调处理数组,确保回调按注册顺序执行
    self.fulfilledQueues = []; //成功回调函数队列
    self.rejectedQueues = []; // 失败回调函数队列

    // 2.1.2 一旦状态发生改变,不可逆,并且会清空回调队列
    function resolve(result) {
        // 如果value是promise, 则一直展开
        // 直至value为非promise为止
        if (result instanceof _Promise) {
            return result.then(resolve.bind(this), reject.bind(this));
        }

        const run = () => {
            self.state = FULFILLED;
            self.result = result;

            // 依次执行成功队列中的函数,并清空队列
            let fulfillCallback;
            while ((fulfillCallback = self.fulfilledQueues.shift())) {
                fulfillCallback(result);
            }
        };

        // 为了支持同步的Promise,这里采用异步调用
        if (self.state === PENDING) {
            setTimeout(run, 0);
        }
    }

    function reject(reason) {
        const run = () => {
            self.state = REJECTED;
            self.reason = reason;

            // 依次执行失败队列中的函数,并清空队列
            let rejectCallBack;
            while ((rejectCallBack = self.rejectedQueues.shift())) {
                rejectCallBack(reason);
            }
        };

        // 为了支持同步的Promise,这里采用异步调用
        if (self.state === PENDING) {
            setTimeout(run, 0);
        }
    }

    try {
        // 马上执行传入的函数,并把resolve和reject函数作为参数传进去
        executor(resolve, reject);
    } catch (error) {
        reject(error);
    }
}

// 2.2 有一个then方法,接受 onFulfilled 和 onRejected 参数
_Promise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    const { state } = self;

    // 2.2.1 onFulfill和onRejected为可选参数,且为函数
    // 2.2.5 两个参数必须作为函数被调用
    // Promise值的穿透,当then无参数时,默认参数就是把值往后传或者抛
    onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;

    onRejected = isFunction(onRejected)
        ? onRejected
        : (error) => {
              throw error;
          };

    // 2.2.6 then可以被调用多次,因此需要维护回调队列,根据顺序执行回调

    // 2.2.7 then返回一个promise
    // 2.2.7.1 onFulfilled onRjected执行结果为x,调用resolvePromise
    // 2.2.7.2 onFulfilled onRejected执行时抛的异常,在newPromise需reject抛出

    let newPromise = new _Promise((resolve, reject) => {
        if (state === PENDING) {
            // 在pending时会将回调函数注册到回调队列里面,状态发生变化后再根据状态执行对应的回调
            // 这里之没有异步执行,是因为这些函数必然会被resolve或reject调用,而在构造函数里resolve和reject的实现已是异步
            self.fulfilledQueues.push((result) => {
                try {
                    let x = onFulfilled(result);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            });

            self.rejectedQueues.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            });
        }

        // 2.2.2 onFulfilled 必须在fulfilled时调用
        // 2.2.4 onFulfilled/onRejectd为微任务
        if (state === FULFILLED) {
            setTimeout(function() {
                try {
                    let x = onFulfilled(result);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            }, 0);
        }

        // 2.2.3 onRejected 必须在rejected时调用
        // 2.2.4 onFulfilled/onRejectd为微任务
        if (state === REJECTED) {
            setTimeout(function() {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch (error) {
                    reject(error);
                }
            }, 0);
        }
    });

    return newPromise;
};

// 2.3 resolvePromise
function resolvePromise(promise2, x, resolve, reject) {
    // 2.3.1 x为promise2本身,就抛TypeError
    if (x === promise2) {
        return reject(new TypeError("Chaining cycle detected for promise"));
    }

    // 2.3.2 x为一个新的promise
    if (isPromise(x)) {
        return x.then(resolve, reject);
    }

    // 2.3.3 x为一个对象或函数(又分thenable和非thenable)
    // 2.3.3.3 如果x.then是一个函数,执行x.then.call()
    // 2.3.3.4 如果 x.then不为函数,直接返回一个fulfilled的promise,result = x

    // 2.3.4 x不为一个对象和函数,直接返回一个fulfilled的promise,result = x

    // 整理了一下以上几点,大概简化了成以下逻辑:
    // 如果为thenable,需要一个过程去将 thenable 转成 Promise,并且会立即调用 thenable 对象里面 then 的方法,重新进入resolvePromise过程(相当于额外多了一个PromiseResolveThenableJob的微任务);
    // 否则 resolve(x)

    // 2.3.3.1 取then函数
    // 2.3.3.2 如果 x.then出错,则reject promise2 whth error
    if (isThenable(x)) {
        try {
            const then = x.then;
            then.call(
                x,
                function(v) {
                    return resolvePromise(promise2, v, resolve, reject);
                },
                function(error) {
                    return reject(reason);
                }
            );
        } catch (error) {
            return reject(error);
        }
    }

    resolve(x);
}

# Promise 的一些 API 实现

# catch 和 finally

// then和finally一般作为链式调用,因此直接挂在prototype上面
_Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
};

// finally总是最后调用的,不管成功或者失败都被会调用。
_Promise.prototype.finally = function(fn) {
    // 貌似对finally的行为没有一个公认的定义,所以这个实现目前是跟Q保持一致,会返回一个新的Promise而不是原来那个。

    // this.then的时候已经算是最后的一个promise实例,被传递至finally函数里
    return this.then(
        function(v) {
            setTimeout(fn);
            return v;
        },
        function(r) {
            setTimeout(fn);
            throw r;
        }
    );
};

# resolve 和 reject

_Promise.resolve = function(val) {
    var promise = new _Promise(function(resolve, reject) {
        resolvePromise(promise, val, resolve, reject);
    });
    return promise;
};

_Promise.reject = function(reason) {
    return new _Promise(function(resolve, reject) {
        reject(reason);
    });
};

# race

// Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise被resolve或reject,返回的 promise就会被resolve或reject。
_Promise.race = function(promises) {
    return new _Promise(function(resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            _Promise.resolve(promises[i]).then(
                function(val) {
                    return resolve(val);
                },
                function(reason) {
                    return reject(reason);
                }
            );
        }
    });
};

# all

Promise.all(iterable) 方法返回一个 promise,需要等待迭代器中的全部 promise 被 resolve,返回的 promise 才会被 resolve;否则,若中间某一个 pomise 被 reject 或者出错,则返回的新 promise 会被 reject。

_Promise.all = function(promises) {
    return new _Promise(function(resolve, reject) {
        const promiseNum =  promises.length;

        let resolvedCounter = 0let resolvedValues = new Array(promiseNum);

        for (let i = 0; i < promiseNum i++) {
            Promise.resolve(promises[i]).then(
                function(value) {
                    resolvedCounter++;
                    resolvedValues[i] = value;

                    // 当且仅当所有promise都被resolve才会return resolve,且把resolvedValues作为结果抛出去
                    if (resolvedCounter == promiseNum) {
                        return resolve(resolvedValues);
                    }
                },

                function(reason) {
                    return reject(reason);
                }
            );
        }
    });
};

# allSettled

Promise.allSettled 用法类似于 Promise.all,接受一个由 promise 异步任务组成的数组,并支持同时处理多个 promise 异步任务。

和 all 的区别就是,不管其中的 promise 元素失败还是成功,都会被 resolve,触发 then 的第一个函数,结果由包含每个 promise 的 status 和 result/reason 对象组成。

// Promise.allSettled(iterable)方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。
_Promise.allSettled = function(promises) {
    promises = Array.from(promises);

    return new _Promise(function(resolve, reject) {
        const promiseNum = promises.length;
        let result = new Array(promiseNum);
        let count = 0;

        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promise[i]).then(
                function(result) {
                    count++;
                    result[i] = { state: "fulfilled", result };

                    if (count === promiseNum) {
                        resolve(result);
                    }
                },

                function(reason) {
                    count++;
                    result[i] = { state: "rejected", reason };

                    if (count === promiseNum) {
                        resolve(result);
                    }
                }
            );
        }
    });
};

# 需注意

# Promise 存在值穿透

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then((result) => console.log(result));

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。上述例子中只会打印1

这也体现在了 promise/A+ 规范里面的 2.2.1 中。

# then 处理错误函数 和 catch 的区别

then 可以接收两个参数,第一个是处理成功的函数,第二个是处理错误的函数。

.catch.then 第二个参数的简便写法(.cath() => .then(undefined, onRejected)),但是它们用法上有一点需要注意:
.then 的第二个处理错误的函数,捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。

Promise.resolve()
    .then(
        function success(res) {
            throw new Error("error");
        },
        function fail1(e) {
            console.error("fail1: ", e);
        }
    )
    .catch(function fail2(e) {
        console.error("fail2: ", e);
    });

// fail2:  Error: error at success (<anonymous>)

.then 的第二个处理错误的函数catch 捕获错误信息的时候会就近原则,如果是 promise 内部报错,reject 抛出错误后,then 的第二个参数和 catch 方法都存在的情况下,只有 then 的第二个参数能捕获到,如果 then 的第二个参数不存在,则 catch 方法会捕获到。

# promise.catch(...).then(...) 和 promise.then(...).catch(...)

  • promise.catch(...).then(...)

catch 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then 方法。

代码运行完 catch 方法指定的回调函数,会接着运行后面那个 then 方法指定的回调函数。

如果没有报错,则会跳过 catch 方法。此时,要是最后的 then 方法里面报错,就与前面的 catch 无关了。

  • promise.then(...).catch(...)

如果 promise 抛出了错误 且 then 中只提供了 onFulfilled,此时 then 会被跳过,将值一直往后传递直到 catch。

参考文章:
剖析 Promise 内部结构 (opens new window)

符合 Promise/A+规范的 Promise 源码实现 (opens new window)

100 行代码实现 Promises/A+ 规范 (opens new window)