目录

  1. 前言
  2. done
    1. 消失的错误
    2. 原理实现
    3. 小结
  3. finally
    1. Why not .then(f, f)?
    2. 原理实现
    3. 小结
  4. done、finally 方法到底谁最后执行?
  5. 总结

前言

Promise 大家再熟悉不过了,Promise 是异步编程的一种解决方案,比传统的解决方案,回调函数和事件更合理和更强大。Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

ES6 的 Promise API 提供的方法不是很多,有些有用的方法可以自己部署。下面介绍如何部署两个不在ES6之中、但很有用的方法。done 方法和 finally 方法。finally 方法大家可能用的比较多,done 方法相对少一点,并且现在这两个方法出现在面试中的概率越来越大了,比如:

  1. done 方法实现原理是什么?你能自己实现一个吗?
  2. finally 方法运行机制,手写一个看看?
  3. done、finally 方法到底谁最后执行?这个可能是一个问答题,也可能是一个看题说结果的题目

面试官.jpeg
这几个问题都是现在问的比较多的,因为 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内部的错误不会冒泡到全局)。我们来看一个例子:

  1. function JSONPromise(value) {
  2. return new Promise(function (resolve) {
  3. resolve(JSON.parse(value));
  4. });
  5. }
  6. // 运行示例
  7. const string = "一个不合法的json字符串";
  8. JSONPromise(string).then(function (object) {
  9. console.log(object);
  10. }).catch(function(error){
  11. // => JSON.parse抛出异常时
  12. console.error(error);
  13. });

由于 string 这个字符串是一个不合法的 JSON 字符串,所以会解析抛出一个错误,然后被catch 捕捉到。正常情况你写了catch 方法正常捕获,但是如果没有写或者漏写了,一旦发生异常,想要查找源头就是一个非常棘手的问题。

  1. function JSONPromise(value) {
  2. return new Promise(function (resolve) {
  3. resolve(JSON.parse(value));
  4. });
  5. }
  6. // 运行示例
  7. const string = "一个不合法的json字符串";
  8. JSONPromise(string).then(function (object) {
  9. console.log(object);
  10. });

这里可能例子比较简单,在实际的研发过程中 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 的话,则进行错误显示的一种机制。FirefoxChrome 的原生Promise都进行了部分实现。

    原理实现

    它的实现代码相当简单。
    1. Promise.prototype.done = function (onFulfilled, onRejected) {
    2. this.then(onFulfilled, onRejected)
    3. .catch(function (reason) {
    4. // 抛出一个全局错误
    5. setTimeout(() => { throw reason }, 0);
    6. });
    7. };
    从上面代码可见,done方法的使用,可以像then方法那样用,提供FulfilledRejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。如果严格一点,也可以这样写:
    1. "use strict";
    2. if (typeof Promise.prototype.done === "undefined") {
    3. Promise.prototype.done = function (onFulfilled, onRejected) {
    4. this.then(onFulfilled, onRejected).catch(function (error) {
    5. setTimeout(function () {
    6. throw error;
    7. }, 0);
    8. });
    9. };
    10. }

    小结

    done 并不返回 Promise 对象,所以在done 之后并不能在使用catch 。done 的错误是直接抛出去的,并不会进行 Promise 的错误处理。Promise具有强大的错误处理机制,而done则会在函数中跳过错误处理,直接抛出异常。
    讲完 done 方法你已经了解到为什么会有 done 的出现,如果自己实现一个,接下来在来看看 finally 方法。

    2. finally

    finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
    1. server.listen(0)
    2. .then(function () {
    3. // run test
    4. })
    5. .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。

    原理实现

    它的实现也很简单。
    1. Promise.prototype.finally = function (callback) {
    2. let P = this.constructor;
    3. return this.then(
    4. value => P.resolve(callback()).then(() => value),
    5. reason => P.resolve(callback()).then(() => { throw reason })
    6. );
    7. };
    上面代码中,不管前面的Promise是fulfilled还是rejected,都会执行回调函数callback

    小结

    finally 方法本质是一个 then 方法,所以在实现方法中要调用 then 方法入参是一个函数,需要在 then 方法中执行这个函数
    使用 Promise.resolve 会等入参的函数执行完再返回结果,并将上一个 then 的 value 返回 reject 方法中需要抛出错误信息。

3. done、finally 方法到底谁最后执行?

在讨论这个问题之前,我们先把 Promise.prototype.finally 转换为 ES5 是什么样的。

  1. "use strict";
  2. Promise.prototype.finally = function (callback) {
  3. let P = this.constructor;
  4. return this.then(value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => {
  5. throw reason;
  6. }));
  7. };

在线转换:https://es6console.com/https://babeljs.io/repl

你是不是明白了什么,要这么写的原因是在于,finally其实并不一定是这个promise链的最后一环,相对而言,其实done才是。因为finally可能之后还有thencatch等等,所以其必须要返回一个promise对象。是不是瞬间秒懂。

总结

今天对 Promise 的 done 方法和 finally 方法进行了一个介绍,也从原理的角度为大家手写了它们的实现,这两个方法看完也可以在项目中使用起来,但是注意兼容性,并不是所有地方都能使用。希望今天的文章对你有帮助。

参考