0%

Promise (II) - then, catch, finally

本文開始

本文內容是跟著這一篇教學內容寫下的紀錄。

上一篇文章有提到,Promise 物件可以用來連結 索取資源部分的程式碼在取得資源後,在利用這個資源做其他事的程式碼 這兩部分的功能。

那這一篇就是說明後者,也就是怎麼在取得資料之後,利用 Promise 物件 thencatch 這兩個成員屬性做後續的處理。

then

先來上個小範例

1
2
3
4
5
6
7
8
let promise = new Promise(function(resolve, reject) {
// executor
})

promise.then(
function(result){ /* 處理執行成功後,回傳的結果 */ },
function(error){ /* 處理執行失敗後,回傳的結果 */ }
)

可以看到 then 這個成員函式可以傳入兩個參數皆為 function,
第一個參數的函式,會在 Promise 物件執行結束,並呼叫 resolve function 回傳結果後被呼叫。

第二個參數的函式,會在 Promise 物件執行結束,並呼叫 reject function 回傳結果後被呼叫。

來上個範例

1
2
3
4
5
6
7
8
const promise = new Promise(function(resolve, reject){
setTimeout(() => {resolve('success')}, 1000)
})

promise.then(
result => console.log(result),
error => console.log(error),
)

上面的範例,就是展示當 Promise 物件執行成功,並呼叫 resolve function 回傳結果後, then 的第一個參數 function 被呼叫的寫法。

再來個範例

1
2
3
4
5
6
7
8
const promise = new Promise(function(resolve, reject){
setTimeout(() => { reject('failed') }, 2000)
})

promise.then(
result => console.log(result),
error => console.log(error),
)

上面的範例,就是展示當 Promise 物件執行失敗,並呼叫 reject function 回傳結果後, then 的第二個參數 function 被呼叫的寫法。

如果我們只在意 Promise 執行成功後的結果的話,上面的範例,可以簡寫成下面這樣

1
2
3
4
5
6
7
const promise = new Promise(resolve => setTimeout(() => {
resolve('success')
}, 1000))

promise.then(
result => console.log(result),
)

或者,你想要讓彈窗顯示執行成功後的結果的話,可以改寫成這樣

1
2
3
4
5
const promise = new Promise(resolve => setTimeout(() => {
resolve('success')
}, 1000))

promise.then(alert)

catch

如果,我們只在意執行錯誤後的結果的話,可以把 then 的第一個參數 function 帶入 null,並只定義第二個 function。

另一個寫法的話,就是用 catch

1
2
3
4
5
6
7
8
9
10
11
12
const promise = new Promise((resolve, reject) => {
setTimeout(() => { reject(new Error('failed')) }, 2000)
})

// 用 then 的寫法
promise.then(
null,
error => console.log(error)
)

// 用 catch 寫法
promise.catch(error => console.log(error))

由此可以看的出來 .catch(f) 的寫法,其實就是 .then(null, f) 的所寫寫法。

finally

當 Promise 物件呼叫完 resolvereject function 後,我們可以通稱它為一個 settled 的 Promise,
跟 Promise 的 state 成員屬性不一樣喔,settled 並不在 state 的值裡面,不要搞混了。

Ok,現在就可以說明 finally 啟動的時機,
當 Promise 已經 settled (i.e. 已經完成呼叫 resolve 或 reject),則 finally function 就會被啟動。

先來上個範例

1
2
3
4
5
new Promise((resolve, reject) => {
setTimeout(() => { resolve('success') }, 2000)
})
.finally(() => console.log('finally')),
.then(result => console.log(result))

上面的範例會發現,
在兩秒後,resolve 被呼叫,
接著會執行 finally function,再來執行 then,並且由 resolve 夾帶的 value 會傳到 then 裡面,不會說因為中間接了一個 finally function 而有所影響。

今天如果把上面的範例改成 Promise 執行失敗,並呼叫 reject

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
setTimeout(() => { reject(new Error('failed')) }, 2000)
})
.finally(() => console.log('finally')),
.then(
null,
error => console.log(error)
)

在兩秒後,reject 被呼叫,
接著會執行 finally function,再來執行 then 的第二個參數 function,並且由 reject 夾帶的 error 會傳到 then 裡面,不會說因為中間接了一個 finally function 而有所影響。

所以,依照以上兩個範例,我們可以看到 finally 有以下特性
finally 沒有參數需要帶入。
.不管今天 Promise 是執行成功或失敗,都會呼叫 finally
finally 不會影響由 rejectresolve 夾帶的結果,該結果會直接傳到後面適合的 handler (若是 resolve就傳給 then ,若是 reject 則傳給 catch,或 then 的第二個參數 function)。

另外,finally 在回傳部分,
照道理來說,finally 是不應該回傳任何東西的,若有回傳,則該回傳的內容會自動的被忽略,

但有例外,如果 finally 丟出 error 的話,則由原本的 Promise 物件回傳的結果,就不再被傳遞到 finally 後面的 handler,轉而是將 finally 丟出的 error 傳遞到後面適合的 handler 來接收。
舉個範例

1
2
3
4
5
6
7
8
9
10
11
12
const promise = new Promise((resolve, reject) => {
setTimeout(() => { resolve('success') }, 1000)
})

promise
.finally(() => {
throw(new Error('failed'))
})
.then(
res => console.log(res),
err => console.log(err)
)

上面的範例,原本的 Promise 是執行成功並呼叫 resolve 來回傳結果,
但因為在 promise 的 stream 裡面,finally 丟出錯誤,所以,後面的 stream 轉而會由 then 的第二個參數 function 接受到這個 error 內容。

根據以上的 finally 的特性,我們可以在 finally 裡寫一些清除狀態的內容,因為,它並不會影響 Promise 執行完成後回傳的結果,並且不管執行成功或失敗都會被呼叫。

❗ 可以為 settled 的 Promise 加入 handler

若我們在已完成呼叫 resolve 或 reject 的 Promise 即 settled 的 Promise,加入其他的 handler (即 then 或 catch),則會立即取得回傳的結果。

Example: loadScript

在文章中的範例,利用 Promise 來改寫原本利用 callback function 來作為 script.onload 完成後要執行的內容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;

script.onload = () => resolve(script)
script.onerror = () => reject(new Error(`Script load error for ${src}`))

document.head.append(script);
})
}

const promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise
.then(
script => alert(`${script.src} is loaded`),
error => alert(`Error: ${error.message}`),
)

promise.then(script => alert('Another handler...'))

Conclusion

  1. Promise 物件的成員函式 then -
    有兩個參數可以定義,皆為 function,
    第一個參數的 function,會在Promise 物件完成呼叫 resolve 後被呼叫。
    第二個參數的 function,會在Promise 物件完成呼叫 reject 後被呼叫。

  2. Promise 物件的成員函式 catch -
    專門用在接收當 Promise 呼叫 reject 的情境,其實就是 then(null, f) 的簡潔寫法。

  3. Promise 物件的成員函式 finally -
    finally 沒有參數需要帶入。
    .不管今天 Promise 是執行成功或失敗,都會呼叫 finally
    finally 不會影響由 rejectresolve 夾帶的結果,該結果會直接傳到後面適合的 handler (若是 resolve就傳給 then ,若是 reject 則傳給 catch,或 then 的第二個參數 function)。
    finally 不會回傳任何內容,若有寫入回傳內容,則回傳的部分會直接被忽略。

finally 若有寫入 throw 的內容,則由原始 Promise 回傳回來的結果會直接被拋棄,轉而由 finally throw 的結果當作後續 stream 要傳遞的內容,並且會由後續適合的 handler(即會被 catch 或者 then 的 第二個參數 function) 來接收。

Reference

  1. https://javascript.info/promise-basics
  2. 有關 finally 的 throw 的執行流程簡介