实现一个 Promise(2)

除了 then 以外的所有方法

上一篇实现了 Promise 的核心方法 then(),本篇将在此基础上实现 Promise 的其它 instance method(实例方法)和 static method(静态方法)。

说明:

  • 在基于 then() 实现其它方法时,选择了用 then-catch-finally 的形式,主要是为了代码的可读性。理论上,底层库更应该用 then() 的双参数形式,毕竟会少返回一两层 promise。

  • 这里就不考虑不同的 Promise 实现之间的 interoperability(互操作)了,比如在 resolve() 里直接判断 x instanceof MyPromise,比如在其它方法里直接调用 MyPromise.resolve()。毕竟 Promise 已经成为语言的正式标准也有七八年了,再加上本文的主要目的是想“通过自己实现一个 Promise 来更好地理解其原理”。

1. 实例方法

then() 是 Promise 的 primitive(原始)方法,catch()finally() 都是基于它实现的。

1.1 Promise.prototype.then()

MyPromise.prototype.then = function (onFulfilled, onRejected) {
    // ...
}

具体的代码实现,详见上一篇:实现一个符合 Promises/A+ 规范的 Promise

1.2 Promise.prototype.catch()

相当于 then() 的简写,代码如下:

MyPromise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
}

1.3 Promise.prototype.finally()

是在 then() 的基础上实现的,也返回一个 promise(便于链式调用),不过逻辑上有以下不同:

  1. 只有一个回调参数,可以理解为形如 then(onFinally, onFinally)

  2. 调用时机:无论是 fulfilled 还是 rejected,都会被调用(因此通常用来处理二者的公共逻辑)

  3. 在调用链上,如何从前面接值:该回调函数没有参数,即不接收上一个 then 的执行结果

  4. 在调用链上,如何向后面传值:

    • 如果回调函数的执行正常,则单纯透传上一个 then 的执行结果(包括 fulfilled 和 rejected)

    • 如果回调函数的执行被 reject 了,则它会把自身的异常结果传下去,即触发下一个 then 的 reject 逻辑

可以运行以下代码,感受 finally() 的用法:

new Promise((resolve, reject) => {
    resolve(1)
    // reject(2)
}).finally((value) => {
    // 1. 始终输出 undefined,因为它的回调函数没有参数
    console.log('finally() value=', value)
    // 2. 若是正常结果,则此返回没人理,因为逻辑上是透传上一个的结果
    return 3
    // 3. 若是有异常了,则会把该异常结果传下去(此时就变成上一个的结果没人理了)
    // return Promise.reject('finally reject()')
    // throw Error('finally Error()')
}).then((value) => {
    console.log('value: ', value)
}, (reason) => {
    console.log('reason:', reason)
})

finally() 的代码示意,如下:

MyPromise.prototype.finally = function (cb) {
    // 若构造函数未知,则可通过 this.constructor 获取
    //     在这个例子里,构造函数就是 MyPromise
    // 为了兼容 `cb()` 返回值的各种情况(同步or异步)
    //     统一将其处理成 Promise,等其执行完了再返回
    return this.then(
        (value) => MyPromise.resolve(cb()).then(() => value),
        (reason) => MyPromise.resolve(cb()).then(() => { throw reason })
    )
}

注意:finally() 的执行顺序取决于它在 then() 链式调用里的顺序,而并不是要“最后”执行。比如:

new Promise((resolve, reject) => {
    resolve()
}).finally(() => {
    console.log('finally1')
}).then(() => {
    console.log('then')
}).finally(() => {
    console.log('finally2')
})
// 会输出:
// finally1
// then
// finally2

结合 finally 函数的功能语义,感觉它叫 settled 会更合适。但是考虑到它的使用场景,通常是处理一些不论是 fulfilled 还是 rejected 的通用逻辑,因此大多数情况都是放在链式调用的最后;即便是在中间位置,也是对上一个 then 的 finally 处理。站在这个角度理解,名字叫 finally 也说得通。

2. 静态方法

2.1 Promise.resolve()

要么返回一个的 promise,要么返回参数本身。

代码示意,如下:

MyPromise.resolve = function (x) {
    return (x instanceof MyPromise) ? x : (new MyPromise((resolve) => resolve(x)))
}

通常,当我们不知道某个值是不是 promise 时,就可以用 Promise.resolve(x) 将其显式地转为 promise。

2.2 Promise.reject()

返回一个的 promise 对象,用给定的 reason 来 reject。

MyPromise.reject = function (r) {
    return new MyPromise((undefined, reject) => { reject(r) })
}

3. 静态方法之并发

async task concurrency,异步任务并发

JavaScript 本质上是 single-threaded(单线程的),因此在给定的瞬间只有一个 task 被执行,虽然看起来是并发的。JavaScript 中的 parallel execution(并行执行)只能通过 worker threads(工作线程)来实现。

Promise 类提供了 4 种 static method 来处理异步任务并发,包括:

  1. Promise.race() ES2015,ES6

  2. Promise.all() ES2015,ES6

  3. Promise.allSettled() ES2020

  4. Promise.any() ES2021

它们都接收一个 iterable promises(准确地说是 thenables),然后返回一个新的 promise。其中,iterable 可能包含一个或多个 non-promise value 或已经 settled 的 promise。

3.0 参数说明

(1)参数必须是 iterable

判断一个值是不是 iterable(可迭代的),代码如下:

const isIterable = function (x) {
    if (x !== undefined && x !== null && typeof x[Symbol.iterator] === 'function') {
        return true
    } else {
        throw new TypeError(`${typeof x} ${x} is not iterable (cannot read property Symbol(Symbol.iterator))`)
    }
}

内置的可迭代对象String, Array, TypedArray, Map, Set, Segments 以及 arguments, NodeListfunction*, async function*

当参数不是 iterable(可迭代的)时,会报错。如下:

rejected: TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean true is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: boolean false is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: number 5 is not iterable (cannot read property Symbol(Symbol.iterator))
rejected: TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

(2)当参数是 empty 时

当参数是 empty iterable 时,不同方法的处理结果不同。如下:

静态方法
iterable
empty iterable 时返回的 promise

race()

必须

永远 pending

all()

必须

fulfilled []

allSettled()

必须

fulfilled []

any()

必须

rejected AggregateError: All promises were rejected

(3)当包含 non-promise value 时

参数里可能包含 non-promise value,所以统一用 Promise.resolve(p) 处理成 promise。

(4)当包含已经 settled 的 promise 时

对于参数里已经 settled 的 promise,依然按照异步的逻辑执行,即不做特殊处理。

不是 pending 状态的 promise,就称它已经 settled 了,既可以 fulfilled 也可以 rejected。

3.1 Promise.race()

返回一个的 promise,用最先 settled 的结果来 settle。

代码示意,如下:

MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            for (let p of promises) {
                MyPromise.resolve(p).then(resolve, reject)
            }
        }
    })
}

3.2 Promise.all()

返回一个的 promise,要么是用所有 values 的数组来 fulfill,要么是用最先到的 reason 来 reject。

代码示意,如下:

MyPromise.all = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                resolve([])
            }

            // iterable
            let result = new Array(len)
            let count = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => {
                        count++
                        result[i] = value
                        if (count === len) {
                            resolve(result)
                        }
                    }).catch(reject)
            }
        }
    })
}

3.3 Promise.allSettled()

返回一个的 promise,用一个 promise state snapshots 数组来 fulfill,且是在所有原始 promises 都 settled 了(可以 fulfilled 也可以 rejected)。

代码示意,如下:

MyPromise.allSettled = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                resolve([])
            }

            // iterable
            let result = new Array(len)
            let count = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => {
                        result[i] = {
                            status: 'fulfilled',
                            value
                        }
                    })
                    .catch((reason) => {
                        result[i] = {
                            status: 'rejected',
                            reason
                        }
                    })
                    .finally(() => {
                        count++
                        if (count === len) {
                            resolve(result)
                        }
                    })
            }
        }
    })
}

3.4 Promise.any()

返回一个的 promise,要么是用最先 fulfilled promise 来 fulfill,要么是用包含所有 reasons 的聚合错误来 reject。

代码示意,如下:

MyPromise.any = function (promises) {
    return new MyPromise((resolve, reject) => {
        // must be iterable promises
        if (isIterable(promises)) {
            const len = promises.length

            // empty iterable
            if (len === 0) {
                throw new AggregateError([], 'empty promises')
            }

            // iterable
            let errors = new Array(len)
            let errorCount = 0
            for (let i in promises) {
                MyPromise.resolve(promises[i])
                    .then((value) => resolve(value))
                    .catch((reason) => {
                        errorCount++
                        errors[i] = reason
                        if (errorCount === len) {
                            reject(new AggregateError(errors, 'All promises were rejected'))
                        }
                    })
            }
        }
    })
}

Last updated