Jeff的隨手筆記

學習當一個前端工程師

0%

『Day -22』Promise

這一篇我想來學習一下Promise。

為什麼說是學習呢?因為我發現我無法跟人解釋這個東西,因此才決定重新學習Promise以及他後續延伸出來的async/await

非同步處理的演進

但在講之前我們必須先知道,他為什麼被創造出來,他是為了改變什麼事情。

讓我們先看一下演進圖:
Imgur

由這邊我們可以得知,在Promise還沒出來前我們都是使用Callback來處理非同步的事情,但相信大家一定都聽過恐怖的Callback Hell 吧。

Imgur

由於使用Callback很容易會產生 callback hell的狀況跟難以維護的code。

因此Promise 就誕生了,他的出現讓我們有:

1.更清晰的code結構:通過Promise,讓非同步操作的部分分離出來,降低了code的複雜性。

2.處理錯誤更容易:主要是因為 Promise提供了.catch() 来捕獲和處理非同步操作中的錯誤。

3.避免再出現Callback Hell :透過.then()將非同步操作連接起來,讓code更加扁平跟可讀。

4.更好的流程控制:我們可以根據Promise的狀態,來決定下一步的操作。

Promis

什麼是Promis

Promise,如字面的意思就代表 承諾

網路上有大大幫我們整理好了Promis的流程圖:

Imgur

圖片來源:https://hackmd.io/@wheat0120/javascript-promise

當我們今天拿到一個Promise 的時候,代表這個 Promise 在之後可能會有幾種狀況發生:

  • 成功 (fulfilled)

    用 resolve() 來兌現

  • 失敗 (rejected)

    用 reject() 來表示失敗

  • 還在執行中 (pending)

    一直沒有回傳

上述3個承諾我們統稱為:**state**。

Promise除了有 state 這個屬性外,還有一個屬性我們需要知道,那就是**result** 。

result執行完 Promise 後的結果值。

當決定好了狀態(state)後,就會依照我們所設定好的程式開始執行。

創建Promise

Promise 是一個物件建構子 (constructor),使用時需要先從 Promise 物件產生物件實例 (instance),再使用繼承特性的 instance 去包裝程式碼的 callback 流程。

基本的 Primise 宣告方式如下:

1
2
3
4
// 建立 instance
var promise = new Promise((resolve, reject) => {
// executor code
})

我們可以看到在 Callback 函式內有兩個引數,分別是 resolve 跟 reject 。

這是由 JS 提供、用來決定 Promise 結果狀態時使用的兩個function :

**resolve**:

  • 在 Promise 成功且结果如预期時調用(invoke)。
  • 調用(invoke) 此 function 會將 Promise 的 state 設定為 成功的狀態(fulfilled)。
  • 執行結果的值會傳遞給此 function,從而設置前面提到的result 為給定的值。
  • 會觸發後續的.then() 部分來處理成功狀態的Promise。

**reject**:

  • 在 Promise 遭遇錯誤情況時調用(invoke)。
  • 調用(invoke) 此 function 會將 Promise 的 state 設定為 失敗的狀態(rejected)。
  • 錯誤訊息會做為參數傳遞給此 function。
  • 會觸發後續的 .catch() 部分来處理拒絕狀態的Promise。

使用Promise

1
2
3
4
// 流程控制
promise
.then(...step1...)
.then(...step2...)

Promise 是用於進行流程控制的物件 (容器),它具備了 callback 的優點,但透過 .then() 來標明流程,而 .then() 之間可以互相鏈結 (chaining),把之前「一層包一層的 callback」,轉換成 .then() 的串接。

我們來看一段code ,在上面有備註,這樣會更清楚:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var error = false
// 建構 Promise object
var person = new Promise((resolve, reject) => {
setTimeout(() => {
if (error) {
return reject('error happened')
}
resolve({
name: 'Jeff',
age: 35,
level: 999
})
}, 100)
})

// 使用 Promise object
// 接到 resolve就執行.then()系列
// 若接到 reject 就執行 catah()
person
.then((man) => {
console.log('Welcome to 2023', man)
return man.name + 'say: hi, 2023'
})
.then((data) => {
console.log(data)
return person
})
.catch(() => {
console.warn(error)
})

驗收

查找資料時看到的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const apiURL = `https://webdev.alphacamp.io/api/lyrics/Coldplay/Yellow.json`

// 阻塞函式
function blockingTest() {
const delay = 2000
const end = Date.now() + delay
console.log('blocking start')
while (Date.now() < end) { }
console.log('blocking end')
}

function promise() {
console.log(1)

new Promise((resolve, reject) => {
console.log(2)
blockingTest()
console.log(3)
fetch(`${apiURL}`)
.then(res => res.json())
.then(res => {
resolve(res)
console.log(4)
})
console.log(5)
}).then(res => {
console.log(6, res)
})

console.log(7)
}

promise()
  • Answer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 1
    // 2
    // blocking start
    // blocking end
    // 3
    // 5
    // 7
    // 4
    // 6, res

你答對了嗎?我承認我答錯了XD

我們一步一步來看。

我們執行了promise這個function ,因此console印出了1。

之後我們新建立了一個Promise,在等待確認state的時候遇到第二個console,因此會印出2。

之後執行了 blockingTest() 這個function,他模擬一個長時間運行的同步操作,阻塞了程式的執行,並且遇到了2個console因此印出 “blocking start” 和 “blocking end”。

接下來遇到了fetch ,會發出一個非同步的請求,因此這段會先丟到event queue 等待,程式會繼續往下執行到console.log(5) 並且印出5。

.then() 由於還不確認state的結果會是fulfilled還是rejected ,因此不執行,程式會繼續往下執行到console.log(7) 並且印出7。

此時event loop監測到stack已經空了,這時候就會把event queue裡正在等待fetch 拉到stack並執行,因為可以確實連接上api,因此.then()就會被執行,到第二個.then()就會確認Promise 的 state 是fulfilled ,並且印出4。

因為Promise 的 state 是fulfilled ,因此就會執行

1
2
3
.then(res => {
console.log(6, res) // 6
})

最後印出6跟res裡的資料。


終於把失去的記憶給找了回來,接下來還有async/await 要找回關於他的記憶。

參考資料:

https://www.cythilya.tw/2018/10/31/promise/

https://hackmd.io/@wheat0120/javascript-promise