📝实现一个 Promise(3)

关于 Promise 的其它问题

1. 如何停止一个 promise 链?

站在实现者的角度,当发生严重错误致使后面的链都没必要执行了的时候,可以返回一个永远 pending 的 promise。

return new Promise(() => { })

但是,它会引入一个新的问题:链式调用后面的所有回调函数,都将无法被垃圾回收器回收。

Promise 应该在执行完所有回调后,删除对所有回调函数的引用以让它们能被回收。在之前自己实现 Promise 时,为了减少复杂度,并没有做这种处理。

思考:能否用一个函数变量或函数定义,来替代上面的匿名函数?答案是可以的,这样的话整个 promise 链就只有一份在内存里,不被回收也是可以接受的。

// 将其封装成一个更有语义的函数,提高可读性
Promise.cancel = Promise.stop = function () {
    return new Promise(() => { })
}

然后,就可以这样停止 promise 链了:

new Promise((resolve, reject) => {
    resolve(35)
}).then((value) => {
    // ...这里发生了严重错误
    return Promise.stop() // 返回一个永远 pending 的 promise
}).catch()
  .then()
  .then()
  .catch()
  .then()

2. 链上最后一个 promise 出错了怎么办?

在所有 promise 链的最后都加上 catch(),是可以确保之前发生的错误都能被捕获。然而,如果是最后一个 catch() 里出错了,如何捕捉?

2.1 新增 done()

方法一:提供一个 done 方法,将其列在链的最后。

// 一定要确保这个函数不能再出错了
Promise.prototype.done = function () {
    return this.catch((e) => {
        console.error(e)
    })
}

就可以这样使用了:

new Promise((resolve) => {
    resolve(35)
}).then((value) => {
    // 这里拼错了
    alter(value)
}).done()
// 此时就会输出 ReferenceError: alter is not defined

本质上,这和开发人员在每个链后自己加 catch 没太大区别,只是函数由框架提供了而已。Q 就使用了 done 方法来达成类似的目的,$q 在最新的版本中也加入了类似的功能。

2.2 完善 reject 逻辑

方法二:在实现 Promise 的 reject 的逻辑里,增加如下判断:

function MyPromise(executor) {
    ...
    
    const reject = (reason) => {
        if (this.status === 'pending') {
            this.status = 'rejected'
            this.reason = reason

            // 当 promise 被 reject 时,判断回调数组的长度
            //   如果为空,则说明它的错误是没有函数处理的
            //   此时,便把错误输出到控制台,以让开发者发现
            if (this.onRejectedArray.length === 0) {
                console.error(reason)
            }

            this.onRejectedArray.forEach((cb) => {
                cb(this.reason)
            })
        }
    }

    ...

    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

比如在执行如下代码时,由于 promise4 是真的没有任何回调,所以就会触发新增的 console.error()

new Promise(() => { // promise1
    reject(3)
}).then() // returns promise2
  .then() // returns promise3
  .then() // returns promise4,错误会一直透传到 promise4

Bluebird 和 ES6 Promise 都做了类似的处理,在 promise 被 reject 但又没有 callback 时,就把错误输出到控制台。

Last updated