上一章節我們提到有一些 JS 的 Web API 會需要在「背景執行」,同時又不影響整個網頁主程式的運行,這些 API 利用瀏覽器 Event Queue 的機制來達成這個目的,也就是所謂非同步的動作。不過難道只有在使用 這些 Web API 的時候,才能使用到非同步的行為嗎?我們有沒有可能讓自己寫的功能,也具有非同步的行為呢?
答案是,可以的,只是方式不太一樣,如果想要讓自己寫的功能也具有非同步的行為,我們會需要用到今天要討論的主角 — Promise 。
Outline
- Promise 簡介
- Promise : 成敗之間
- 成功的 Promise : Succeed and then
- 失敗的 Promise : Catch with an error
- Promise 概念圖
- Promise : 一個生活化的例子
Promise 簡介
Promise 是什麼呢?以語法字面上的意義來看,用比較白話的方式解釋的話有一種:「我承諾幫你做某件事情,能不能成功還不一定,但是我做完之後會把結果告訴你」的意思。
那麼來看看比較技術層面的定義,在官方文件中的定義則是:
Promise 是一個代表非同步運作的最終狀態的物件 (成功或失敗)
A Promise is an object representing the eventual completion or failure of an asynchronous operation. (MDN)
雖然技術文件的解釋就顯得比較抽象,不過從上面看得出來 Promise 在 JS 裡面是以物件的方式存在,那麼接下來我們就來看看要怎麼使用 Promise 吧,基本的 Primise 宣告方式如下:
let promise = new Promise((resolve, reject) => {
// executor code
})
我們以 Callback 的方式來告訴 Promise ,接下來我們定義的非同步函式要做什麼事情,而且也必須跟 Promise 說,做完想做的事情,得到結果後,怎樣的結果算是成功,怎樣的結果算是失敗?這些都會被記錄在這個 Promise 物件裡面,Promise 物件裡面有幾個相關屬性:
- state (狀態) :一個 Promise 裡一共會有三種狀態:
- fulfilled :成功的狀態
- rejected:失敗的狀態
- pending :還在執行中的狀態
- result : 執行完 Promise 後的結果值
Promise : 成敗之間
那要定義 Promise 的運行結果? 你可以看到在 Callback 函式內有兩個引數,分別是 resolve 跟 reject ,就是由 JS 提供、用來決定 Promise 結果狀態時使用的兩個函式 :
-
resolve 用在 Promise 成功且結果如預期時,呼叫這個函式會把 Promise 的 state 設為 fulfilled ,將執行結果數值傳入這個函式會讓上述提到的 Promise 的 result 設為給定的值。
什麼意思呢?下面的程式碼就是一個 Promise 成功,並且把 result 設為
'Success'
的範例:
let promise = new Promise((resolve, reject) => {
resolve(' Success ')
})
-
reject 則與 resolve ,呼叫 reject 會將 state 設為 rejected ,意即失敗。
let promise = new Promise((resolve, reject) => {
if(someValueIwant){
//do other things and resolve
}else{
reject(‘Failure’)
}
})
成功的 Promise : Succeed and then
寫到這邊有個要注意的重點是,上述提到 Promise 的兩個值 state 跟 result 是沒有辦法直接被取用的,他們只能透過某種方式被取用。所以這邊要講的是 then 函式,then 是指在 Promise 順利執行完成後,要取得結果值的方法。
在前面我們提到,Promise 的 callback 內,我們可以將取得的結果值丟給 resolve 函式,之後我們就可以夠過 .then 來取得這個結果,然後做其他事情。then 函式 一樣接收的是一個 callback ,並且帶有一個參數,這個參數就是 Promise 剛剛計算完的結果,以上述例子為例的話就像這樣:
let promise = new Promise((resovlve,reject)=>{
//after some calculation
let result = 'value from some where'
resolve(result)
})
promise.then(result => {
//use result to do something
})
而為什麼要使用 .then 與 callback 的方式呢?因為這樣一來,JS 可以保證這個 callback 在 Promise 執行完之後才被呼叫。
失敗的 Promise : Catch with an error
如果一個 Promise 因為某些原因而被 reject ,那麼上面提到的 .then 裡的 calback 就不會被執行,相反的,他會執行另外一個 callback — 在 .catch 函式內被傳入的 callback。這裡提到的 catch 的用途有點像是在捕捉錯誤時的語法:try & catch 裡的 catch 部分,都是用在錯誤發生時。
let promise = new Promise((resovlve,reject)=>{
//after some calculation
let error = 'some error happended!!'
if (!result){
reject( error )
}
})
promise.catch(error => {
// log the error
})
Promise 概念圖
「狀態」的概念對使用 Promise 來說是很重要的事情,那麼讓我用一張簡單的狀態圖來表示運行的順序吧,首先 Promise 會有一段執行的時間,所以直到剛剛說的 resolve 函式被執行之前,狀態都會是 pending ,而在這之後如果 resolve 被順利呼叫,Promise 的狀態就會變成 fulfilled ,否則就會是 rejected:
Promise : 一個生活化的例子
前面提到,一個 Promise 會有三種狀態:fulfilled 、reject 與 pending 。其實在我們生活中就常常遇到這樣的例子,那就是提款機啦!回想一下剛才提到的「狀態」,提款機其實剛好就有剛剛說的三種狀態可以類比到 Promise 上面!
使用提款機送出提款的要求時,會需要等待一段時間,這時候可以看成 Promise 的執行時間,也就是 pending ,那麼在執行完畢後,可能會發生兩種結果:一種是沒什麼問題 ( fulfilled 的狀態 ),提款機就直接吐錢出來 ( then );另一種是你的餘額不夠,那麼 ATM 直接進入 rejected 拒絕讓你提款,並解顯示錯誤訊息( catch )。