iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
自我挑戰組

重新複習JavaScript系列 第 22

[Day -22] Promise

  • 分享至 

  • xImage
  •  

這一篇我想來學習一下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 宣告方式如下:

// 建立 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

// 流程控制
promise
	.then(...step1...)
	.then(...step2...)

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

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

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)
	})

驗收

查找資料時看到的案例:

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
    // 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 ,因此就會執行

.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


上一篇
[Day -21] Event Loop
下一篇
[Day -23] async function /await
系列文
重新複習JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言