Promise 學習筆記
這篇文章主要是為了複習與更加深入掌握 Javascript Promise
而產生的筆記。在直接閱讀關於 Promise A+ 或原理說明之前先通過比喻的方式理解可以更加深記憶。如果您對於 Promise 還是有點似懂非懂,不妨可以看看。
理解 Promise
Promise 簡單來說:
想像一下你是個孩子,你媽承諾(Promise)
你下個禮拜會送你一隻新手機。
現在你並不知道下個禮拜你會不會拿到手機。你媽可能真的買了新手機給你,或者因為你惹她不開心而取消了這個承諾。
這就是一個 Promise
,一個 Promise 有三種狀態:
pending
未發生、等待的狀態。到下週前,你還不知道這件事會怎樣。resolved
完成/履行承諾。你媽真的買了手機給你。rejected
拒絕承諾。沒收到手機,因為你惹她不開心而取消了這個承諾。
總結來說狀態有 等待
、成功
、失敗
,使用情境就是我們當前還不知道結果,需要等待結果發生才繼續後續的處理
。
建立一個 Promise
看完上面的比喻讓我們對應到 Javascript。
1 | var isMomHappy = false |
上面這段程式碼應該已經充分解釋概略的觀念。
- 第一行我們使用一個 Boolean
isMomHappy
定義媽媽是否開心。 - 我們宣告一個 Promise
willIGetNewPhone
。這個 Promise 可能是被履行(resolved)
又或者拒絕(rejected)
。 - Promise 標準的語法可以參考 MDN
1 | new Promise(function (resolve, reject) {}) |
- 我們需要記得的是如果一個 Promise 執行成功要在內部 function 呼叫
resolve(成功結果)
,如果結果是失敗則呼叫reject(失敗結果)
。在我們的範例中如果媽媽開心,我們將得到手機因此我們執行reslove(phone)
,如果媽媽不高興則執行reject(reason)
。
一個 Promise 物件表達的是一件非同步的操作最終的結果,可以是
成功
或失敗
。
使用 Promise
到這一步我們已經有了一個 Promise,讓我們接著來使用它。
1 | var askMom = function () { |
- 首先我們有個 function 叫
askMom
在這個 function 中,我們將利用 PromisewillIGetNewPhone
。 - 我們希望一旦
等待的結果發生時
可以採取對應的動作,我們可以使用.then
或.catch
來執行對應的行為。 - 在這個範例中,我們在
.then
中使用function (fulfilled){}
,而這個fulfilled
就是從 Promise 的resolve(成功結果)
傳來的結果,範例中這個結果就是phone
物件。 - 在
.catch
中我們使用了function (error) {}
。而這個error
就是從 Promise 的reject(失敗結果)
傳來的即reason
。
鏈式調用 Promise
Promise 是可串連的。
假如你承諾
您的朋友,如果你拿到新手機會借他們看看。這又是另一個 Promise
。讓我們繼續來撰寫這個範例:
1 | var showOff = function (phone) { |
- 在這個範例,您可能發現到我們根本沒有呼叫
reject
,這是可選的,我們可以省略不調用。 - 另外,我們可以透過使用
Promise.resolve
簡化這個範例。
1 | var showOff = function (phone) { |
接著讓我們來看看如何串連 Promise
。在 willIGetNewPhone
這個 Promise 之後接續 showOff
Promise。
1 | var askMom = function () { |
這就是 Promise
串連的方式。
非同步
Promise
是非同步的,讓我們在呼叫 Promise 的前後加上 console.log
1 | var askMom = function () { |
關於上面這段程式碼,您認為 log 的順序會如何?大概我們會猜
1 | 1. before asking Mom |
然而真正的順序是
1 | 1. before asking Mom |
為什麼?因爲時間是不等人的 XD。這也是我們在寫 JS 的時候非常常見得情況。
想像您還是那個孩子,在你媽決定買手機給你之前難道你就一直待在那等結果嘛!?你應該是跑去玩了吧!
這就是我們說的非同步
,程式碼不會卡在那等待結果。如果需要等待結果才能繼續處理的部分,我們就把它方到 then
裡面。
ES5, ES6/ES2015,ES7/Next 的 Promise
ES5
目前主流瀏覽器使用的 JS 版本。上面的範例如果想在全部的瀏覽器運行我們需要加入 Promise 函式庫 - Bluebird。這是因為 ES5 內建不支援 Promise,當然時至今日已經有許多瀏覽器陸續支援了。
ES6/ES2015
Nodejs v6+ 開始原生支援 Promise,此外還有 arrow function
、const
和 let
讓我們來看看 ES6 的程式碼
1 | const isMomHappy = true |
看到所有的 var
被換成 const
然後 function
換成 (resolve, reject) =>
關於更深入的介紹可以參考
- JavaScript ES6 Variable Declarations with let and const
- An introduction to Javascript ES6 arrow functions
ES7
到了 ES7 更引進 async await
讓處理非同步
的語法更加簡潔與具備可讀性。
1 | const isMomHappy = true |
- 當我們要在一個 function 中回傳 Promise 時,我們需要在該 function 前加上
async
- 當我們使用一個 Promise 或者 function 回傳 Promise (async)時需要使用
await
例如let phone = await willIGetNewPhone
或let message = await showOff(phone)
- 使用
try {} catch (error) {}
來攔截例外,即 Promisereject
時。
為何及何時使用 Promise
在我們概略的介紹之後您肯定有些疑問,首先是為什麼我們需要 Promise?以及在沒有 Promise 之前的世界又是怎麼樣。在回答這些問題之前讓我先談談一些基礎的東西
Function 與 Async Function
讓我們先來看看範例:使用函式來加總兩個數字
1 | // 普通的函式加總兩數 |
1 | // 呼叫遠端函式加總兩數 |
如果我們在本地端加總兩數,基本上我們會立刻得到其值,然而如果是透過遠端執行(呼叫 API)則需要等待其回應,不會立刻得到值。
或者這麼說,我們其實不知道 server 需要多久時間,甚至是否正常運作等等,這種情況下我們又不希望介面操作卡在這邊等待。於是像調用 API,下載檔案,讀檔等這些會需要花點時間的操作,在 JS 中通常都是使用非同步的機制來處理。
在 Promise 出現之前:
難道我們一定要用 Promise 來處理非同步嗎?不是,在 Promise 出現之前我們已經有個方式處理 - Callback。所謂的 Callback 就是一個 function ,當我們調用非同步的 function 時,我們把它當作參數一起傳出去,當非同步的處理完成後,會把結果丟進這個 function 繼續處理。讓我們來看看範例:
1 | function addAsync (num1, num2, callback) { |
目前這個範例看起來還OK啊,為什麼我們需要 Promise?
假如我們需要連續處理多個非同步的行為?
現在假如我們不是只有相加兩數,我們想要相加 3 次,如下這個情況
1 | let resultA, resultB, resultC |
如果是上面這個情況使用 callback 呢?
1 | let resutlA, resultB, resultC |
這樣不斷內嵌的語法看起來就不是這麼友善了,在實務上我們非常容易遇到這個情形,例如:需要先使用 API 取得帳號資料,接著該帳戶的訂單,再取出某筆訂單的詳細資料。像上面這樣的程式碼通常人們稱為 callback hell
因為我們需要不斷的內嵌 callback,想像一下如果我們需要 10 層 callback 那會怎樣!
逃離回呼地獄(Callback Hall)
在大部分的程式語言中您比較少機會遇到這樣的問題,主要是因為它們以同步的機制為主,但在大量使用非同步的 JS 中我們就需要解決這個問題了。於是 Promise 就是為了解決這個問題而產生的。讓我們再來看看 Promise 的版本。
1 | let resultA, resultB, resultC |
透過 Promise 我們使用 .then
把 callback hall
不斷內嵌的語法給攤平了。
後起之秀 - Observables
在我們決定使用 Promise 之前,其實還有一個也是用來處理上面這些問題的新方法 Observables
。
Observable 是 lazy event stream 可以觸發 0 到 多個事件,這些事件也不見得需要完成。
相比,Observable 和 Promise 相比,主要的差異為:
- Observable 是可以被取消的。
- Observable 在需要時才會執行。
下面是使用 RxJS 的範例
1 | let Observable = Rx.Observable |
Observable.fromPromise
會將 Promise 轉換成 observable stream。.do
和.flatMap
是 Observable 的運算子。- 所謂的需要時才執行(
lazy
)意思是當我們呼叫.subscribe
時才會開始執行。
Observable 可以將一些複雜的處理流程變的容易。舉例來說我們可以只用一行程式碼去 delay
延遲相加函式 3 秒
1 | addAsync(1, 2) |
Promise 學習筆記