面试官很忙系列:Promise 的 done、finally 那些事

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战git

前言

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

ES6 的 Promise API 提供的方法不是不少,有些有用的方法能够本身部署。下面介绍如何部署两个不在 ES6 之中、但颇有用的方法。done 方法和 finally 方法。finally 方法你们可能用的比较多,done 方法相对少一点,而且如今这两个方法出如今面试中的几率愈来愈大了,好比:github

  1. done 方法实现原理是什么?你能本身实现一个吗?
  2. finally 方法运行机制手写一个看看?
  3. done、finally 方法到底谁最后执行

这个多是一个问答题,也多是一个看题说结果的题目。面试

image.png

这几个问题都是如今问的比较多的,由于 Promise 其余的相关问题都已经被你们所熟悉了,今天我来看看这几个不被你们熟悉的问题。编程

1. done

若是你使用过 Promise 类库的话,你可能见过 done 方法,Promise 类库提过Promise.prototype.done ,用 done 方法来替代 then 方法。在 Promise 规范和 Promise+ 规范中并无对 Promise.prototype.done 作任何的规范,那为何会出现这个方法了。一切都源于那些 “消失的错误”json

消失的错误

咱们先回忆一下 Promise 的特色。“对象的状态不受外界影响”,“一旦状态改变,就不会再变,任什么时候候均可以获得这个结果”。也回忆一下 Promise 的缺点“没法取消 Promise ,一旦新建它就会当即执行,没法中途取消”,“当处于 Pending 状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成) ”,“若是不设置回调函数, Promise 内部抛出的错误,不会反应到外部”。promise

看到最后一条缺点你可能明白了,Promise 无论以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能没法捕捉到(由于Promise内部的错误不会冒泡到全局)。咱们来看一个例子:babel

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 方法正常捕获,可是若是没有写或者漏写了,一旦发生异常,想要查找源头就是一个很是棘手的问题。markdown

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 处理不就行了,这样无疑是最好的。可是并非每个人都像你这样优秀😁。若是在实现的过程当中出现了这个例子中的错误的话,那么进行错误排除的工做也会变得困难。dom

消失的错误还有一个专业名词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都进行了部分实现。

原理实现

它的实现代码至关简单。

Promise.prototype.done = function (onFulfilled, onRejected) {
  this.then(onFulfilled, onRejected)
    .catch(function (reason) {
      // 抛出一个全局错误
      setTimeout(() => { throw reason }, 0);
    });
};
复制代码

从上面代码可见,done方法的使用,能够像then方法那样用,提供FulfilledRejected状态的回调函数,也能够不提供任何参数。但无论怎样,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可能以后还有thencatch等等,因此其必需要返回一个promise对象。是否是瞬间秒懂。

总结

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

若是你以为写得不错,帮忙点个赞吧。

参考

相关文章
相关标签/搜索