前言
Promaise 大家再熟悉不过了,Promise 是异步编程的一种解决方案,比传统的解决方案,回调函数和事件更合理和更强大。Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
ES6 的 Promise API 提供的方法不是很多,有些有用的方法可以自己部署。下面介绍如何部署两个不在 ES6 之中、但很有用的方法。done 方法和 finally 方法。finally 方法大家可能用的比较多,done 方法相对少一点,并且现在这两个方法出现在面试中的概率越来越大了,比如:
- done 方法实现原理是什么?你能自己实现一个吗?
- finally 方法运行机制,手写一个看看?
- done、finally 方法到底谁最后执行?
这个可能是一个问答题,也可能是一个看题说结果的题目。
几个问题都是现在问的比较多的,因为 Promise 其他的相关问题都已经被大家所熟悉了,今天我来看看这几个不被大家熟悉的问题。
1. done
如果你使用过 Promise 类库的话,你可能见过 done 方法,Promise 类库提过Promise.prototype.done
,用 done 方法来替代 then 方法。在 Promise 规范和 Promise+ 规范中并没有对 Promise.prototype.done 做任何的规范,那为什么会出现这个方法了。一切都源于那些 “消失的错误” 。
消失的错误
我们先回忆一下 Promise 的特点。“对象的状态不受外界影响”,“一旦状态改变,就不会再变,任何时候都可以得到这个结果”。也回忆一下 Promise 的缺点“无法取消Promise
,一旦新建它就会立即执行,无法中途取消”,“当处于Pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成) ”,“如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部”。
看到最后一条缺点你可能明白了,Promise 不管以then
方法或catch
方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。我们来看一个例子:
function JSONPromise(value) { return new Promise(function (resolve) { resolve(JSON.parse(value)); }); } // 运行示例 const string = "一个不合法的json字符串"; JSONPromise(string).then(function (object) { console.log(object); }).catch(function(error){ // => JSON.parse抛出异常时 console.error(error); });
由于 string 这个字符串是一个不合法的 JSON 字符串,所以会解析抛出一个错误,然后被catch
捕捉到。正常情况你写了catch
方法正常捕获,但是如果没有写或者漏写了,一旦发生异常,想要查找源头就是一个非常棘手的问题。
function JSONPromise(value) { return new Promise(function (resolve) { resolve(JSON.parse(value)); }); } // 运行示例 const string = "一个不合法的json字符串"; JSONPromise(string).then(function (object) { console.log(object); });
这里可能例子比较简单,在实际的研发过程中 Promise 的使用肯定是比这个例子复杂得多,而且代码的异常也可能是多种多样的。但是,由于 Promise 的 try-catch 机制,这个问题可能就会在 Promise 的内部消化掉,也就是所谓的消失的错误。当然有的同学会说我每次调用进行 catch
处理不就好了,这样无疑是最好的。但是并不是每一个人都像你这样优秀😁。如果在实现的过程中出现了这个例子中的错误的话,那么进行错误排除的工作也会变得困难。
消失的错误还有一个专业名词unhandled rejection,意思就是 Rejected 时没有找到相应处理的意思。在很多 Promise 类库中对unhandled rejection都会有相应的处理。例如:
- ypromise 在检测到 unhandled rejection 错误的时候,会在控制台上提示相应的信息。【Promise rejected but no error handlers were registered to it】
- Bluebird 在比较明显的人为错误,即ReferenceError等错误的时候,会直接显示到控制台上。【Possibly unhandled ReferenceError. xxx】
- 原生(Native)的 Promise 实现为了应对同样问题,提供了GC-based unhandled rejection tracking功能。该功能是在 promise 对象被垃圾回收器回收的时候,如果是 unhandled rejection 的话,则进行错误显示的一种机制。Firefox 或 Chrome 的原生Promise都进行了部分实现。
原理实现
它的实现代码相当简单。
Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, onRejected) .catch(function (reason) { // 抛出一个全局错误 setTimeout(() => { throw reason }, 0); }); };
从上面代码可见,done
方法的使用,可以像then
方法那样用,提供Fulfilled
和Rejected
状态的回调函数,也可以不提供任何参数。但不管怎样,done
都会捕捉到任何可能出现的错误,并向全局抛出。如果严格一点,也可以这样写:
"use strict"; if (typeof Promise.prototype.done === "undefined") { Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, onRejected).catch(function (error) { setTimeout(function () { throw error; }, 0); }); }; }
小结
done
并不返回 Promise 对象,所以在done
之后并不能在使用catch
。done 的错误是直接抛出去的,并不会进行 Promise 的错误处理。Promise具有强大的错误处理机制,而done
则会在函数中跳过错误处理,直接抛出异常。
讲完 done 方法你已经了解到为什么会有 done 的出现,如果自己实现一个,接下来在来看看 finally 方法。
2. finally
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与done
方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
server.listen(0) .then(function () { // run test }) .finally(server.stop);
Why not .then(f, f)
?
其实本质上 finally(func)与 then(func,func)类似,但是在一些关键方面有所不同:
- 内联创建函数时,您可以传递一次,而不必被强制声明两次或为其创建变量
- 由于没有可靠的方法来确定 Promise 是否已兑现,因此 finally 回调将不会收到任何参数。正是这种用例适用于您不关心拒绝原因或实现价值,因此不需要提供它的情况。
- 与 Promise.resolve(2).then(() => {}, () => {}) (将使用未定义的解析)不同,Promise.resolve(2).finally(() => {}) 将用2.解决
- 同样,与Promise.reject(3).then(() => {}, () => {})(将使用未定义的解析)不同,Promise.reject(3).finally(() => {})将被拒绝3。
原理实现
它的实现也很简单。
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ); };
上面代码中,不管前面的Promise是fulfilled
还是rejected
,都会执行回调函数callback
。
小结
finally 方法本质是一个 then 方法,所以在实现方法中要调用 then 方法入参是一个函数,需要在 then 方法中执行这个函数
使用 Promise.resolve 会等入参的函数执行完再返回结果,并将上一个 then 的 value 返回 reject 方法中需要抛出错误信息。
3. done、finally 方法到底谁最后执行?
在讨论这个问题之前,我们先把 Promise.prototype.finally 转换为 ES5 是什么样的。
"use strict"; Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then(value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason; })); };
在线转换:es6console.com/,babeljs.io/repl
你是不是明白了什么,要这么写的原因是在于,finally
其实并不一定是这个promise
链的最后一环,相对而言,其实done
才是。因为finally
可能之后还有then
和catch
等等,所以其必须要返回一个promise
对象。是不是瞬间秒懂。
总结
今天对 Promise 的 done 方法和 finally 方法进行了一个介绍,也从原理的角度为大家手写了它们的实现,这两个方法看完也可以在项目中使用起来,但是注意兼容性,并不是所有地方都能使用。希望今天的文章对你有帮助。
如果你觉得写得不错,帮忙点个赞吧。