在 Node.js 中使用 Promise.prototype.finally

發表於2018-06-11

Promise.prototype.finally()  最近達到了 TC39 提案的 第 4 階段 。這意味著 Promise.prototype.finally() 提案被採納成為 ECMAScript 最新特性草案 的一部分,登陸 Node.js 現在只是時間問題了。這篇文章會向大家展示 Promise.prototype.finally() 的用法和簡化版 Polyfill 的寫法。

在 Node.js 中使用 Promise.prototype.finally

Promise.prototype.finally() 是什麼?

假設你建立了一個新的 Promise:

你可以用 .then() 函式把這些 Promise 串聯在一起。

注意 .then() 需要兩個函式作為引數。第一個引數是 onFulfilled(),當 Promise 為 fulfilled 時呼叫;第二個 onRejected() 則是在 rejected 的時候呼叫。Promise 是一個必定處於以下三種狀態之一的狀態機:

  • pending(進行中): Promise 中的操作正在進行中,狀態未被凝固為 fulfilled 或 rejected。
  • fulfilled(已完成,直譯:已滿足): Promise 中的操作已成功完成,現在 Promise 裡面關聯有該操作的返回值。
  • rejected(已失敗,直譯:已回絕): Promise 中的操作因某些原因失敗,現在 Promise 裡面關聯有該操作的錯誤資訊。

此外,處於 fulfilled 或者 rejected 狀態的 Promise 稱作“已凝固”(settled) 的 Promise。

在 Node.js 中使用 Promise.prototype.finally

雖然 .then() 是串聯 Promise 的核心機制,但並不獨一無二。Promise 用來處理丟擲錯誤的 .catch() 函式 也能串聯 Promise。

.catch() 函式只是一個只有 onRejected() 引數的 .then() 的語法糖:

類似於 .catch(),.finally() 也是 .then() 的一個語法糖。區別在於 .finally() 當 Promise 凝固(fulfilled / rejected)時執行一個 onFinally 函式。當前 .finally() 還沒有加入 Node.js 發行版,但 npm 上的 promise.prototype.finally 模組 實現了它的 Polyfill。

上面程式碼的執行結果會列印 ‘fulfilled’ 和 ‘rejected’,因為無論是 fulfilled 還是 rejected,只要狀態凝固 onFinally 都會立即執行。不過 onFinally 接受引數,所以你無法判斷 Promise 的狀態到底是兩個中的哪個。

finally() 會返回一個 Promise,所以你可以使用 .then() / .catch() / .finally() 串聯它的返回值。finally() 返回的 Promise 會和它連線到的 Promise 保持相同的 fulfill 條件。 例如下面的程式碼,即使 onFinally 返回了 ‘bar’,它還是會列印 5 次 ‘foo’ 。

類似地,下面程式碼中即使 onFinally 沒有丟擲任何錯誤,仍然會列印 ‘foo’。

上面程式碼展示了使用 finally() 的一個重要細節:它 不會 幫你處理 Promise 的錯誤。如何讓它能處理 Promise 錯誤值得更深入的研究。

錯誤處理

finally() 不是 用來處理 Promise 的錯誤的。事實上,它會在 onFinally() 執行後顯式重新拋錯。下面的程式碼會列印一個未被處理的 Promise 錯誤警告。

與 try/catch/finally 類似,通常 .finally() 都會在 .catch() 後面被呼叫。

然而 finally() 返回的也是 Promise,所以你可以隨意在 finally() 後面呼叫 .catch()。特別地,如果 onFinally 會出錯,例如 HTTP 請求,你應該在末尾新增 .catch() 以處理可能發生的錯誤。

簡版 Polyfill

我覺得想要真正搞懂一個東西,最簡單的方式就是自己去實現一個。.finally() 是一個很好的選擇,因為官方 Polyfill 只有 45 行,而且大多數程式碼在驗證原理時可以進一步精簡。

接下來是一些關於 .finally() 的測試樣例。下面的程式碼會列印 ‘foo’ 5 次。

下面是簡版 Polyfill 的實現。

這個實現背後關鍵的思路在於 onFinally 可能返回 Promise。在這種情況下你需要用 .then() 來處理它並且給外層 Promise 凝固狀態。你可以顯式檢查 onFinally 是否返回 Promise,但 Promise.resolve() 已經幫你做了,而且不需要 if 語句。你還需要跟蹤初始 Promise 的值或錯誤,並確保 finally() 返回的 Promise 解析出初始值 res,或重新丟擲初始錯誤 err。

後記

在動筆時,Promise.prototype.finally() 是 8 個 TC39 第四階段提案 之一。這意味著 finally() 將和 7 個其他新語言特性一起加入 Node.js。 finally() 是這 8 個新特性中最令人興奮的之一,皆因為它可以讓非同步操作結束後的清理更徹底。舉個例子,下面我正用在生產環境的程式碼非常需要 finally() 來在函式完成時釋放資源的鎖定。

在 Node.js 中使用 Promise.prototype.finally

相關文章