面試官很忙系列: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 方法進行了一個介紹,也從原理的角度爲你們手寫了它們的實現,這兩個方法看完也能夠在項目中使用起來,可是注意兼容性,並非全部地方都能使用。但願今天的文章對你有幫助。

若是你以爲寫得不錯,幫忙點個贊吧。

參考

相關文章
相關標籤/搜索