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 时,就把错误输出到控制台。