【JS學習筆記】了解 Promise 的基本使用

最後更新於 2021 年 10 月 23 日

JS 是屬於同步的程式語言,一次就做一件事並且依照程式碼由上而下的去執行,因此當遇到非同步事件時就會先等待同步事件執行完畢後再去執行非同步事件。非同步指的是可以同時處理很多件事情,不必等待前一件事情完成才能執行,能夠大大提升效率。在瀏覽器中,雖然 JS 引擎本身是同步的,但 JS 可以和 WebAPI 溝通,來達到非同步的操作。

Promise 很常用到,但是我對 Promise 特別不熟悉,所以決定學習一下並做個筆記。

Promise 是什麼?

Promise 用於非同步操作,是 ES6 新增用來解決非同步回呼地域的新語法,並且 Promise 本身是建構子,依序接收兩個函數參數:resolvereject (實現及拒絕回呼函數)。

在 ES2017之後有了 asyncawait,非同步函數在底層使用了 promise,因此了解 promise 是了解 async 和 await 的基礎。

哪些 JS API 使用了 Promise ?

使用了 promise 的 WebAPI 例子:

  • Battery API
  • Fetch API
  • Service Worker

創建 Promise

可以使用 new Promise() 對其進行初始化,語法如下:

const promise = new Promise((resolve, reject) => {
  //...
});

嘗試輸出 resolve 和 reject 可以知道兩個參數皆為 function

const promise = new Promise((resolve, reject) => {
  console.log("resolve", resolve);
  console.log("reject", reject);
});
console.log(promise);
resolve reject 【JS學習筆記】了解 Promise 的基本使用

Promise 狀態

輸出 Promise 會得到 Promise object 的原型、Promise state 和 Promise result。

promise state 【JS學習筆記】了解 Promise 的基本使用

Promise state 分為:

  1. pending(擱置):初始狀態,事件執行中。
  2. resolved(實現):操作成功並回傳 resolve 結果。
  3. rejected(否決):操作失敗並回傳 reject 結果。

我們可以透過 resolve 和 reject 函數來改變 promise 的狀態:

狀態改變 【JS學習筆記】了解 Promise 的基本使用

當 Promise state 為 resolved 時,可以使用 Promise.then() 來繼續操作。

Promise 方法

Promise 建構子所 new 出來的物件可以使用其原型的方法,包括:then()catch()finally()

Promise.then()

Promise.then() 方法會回傳一個 Promise object,此方法接收兩個 function 參數 onFulfilledonRejected

  • onFulfilled:當 Promise 被實現(fulfilled)時被呼叫。此函式接收一個實現值(fullfillment value)作為引數。
  • onRejected:當 Promise 被拒絕(rejected)時被呼叫。此函式接收一個失敗訊息(rejection reason)作為引數。

舉個例子,假設我使用 throw 拋出異常,Promise 被拒絕就會呼叫 onRejected 函數,並且接收失敗的訊息(失敗啦!)作為引數(reject):

const promise = new Promise((resolve, reject) => {
  throw "失敗啦!";
});
promise.then(
  (resolve) => console.log(resolve + "Success"),
  (reject) => console.log(reject + "Fail")
)
console.log(promise);
error 【JS學習筆記】了解 Promise 的基本使用

Promise.catch()

同樣的,除了 then 之外,還可以再接一個 catch() 來捕捉錯誤:

const flag = false;
const promise = new Promise((resolve, reject) => {
  if (flag) {
  	resolve("Success!");
  } else {
  	reject("Fail!");
  }
})
  .then((data) => console.log(data))
  .catch((error) => console.log("錯誤:",error));
catch 【JS學習筆記】了解 Promise 的基本使用

當 flag 為 false 時就會呼叫 reject 函數,catch 就會捕捉到錯誤訊息並且輸出。

Promise.finally()

無論 promise 操作是 resolve 或 reject,Promise.finally() 中的操作都會執行,並且 finally 不帶有任何參數。

const flag = false;
const promise = new Promise((resolve, reject) => {
  if (flag) {
  	resolve("Success!");
  } else {
  	reject("Fail!");
  }
})
  .then((data) => console.log(data))
  .catch((error) => console.log("錯誤:",error))
  .finally(() => console.log("done"));
finally 【JS學習筆記】了解 Promise 的基本使用

Promise.all()

當需要多個 Promise 同時執行可以使用 Promise.all(),即在所有 promise 都被實現後才會進行其他工作。

Promise.all() 需透過 array 傳入多個 promise 函數,並在全部執行完畢後回傳 array 結果。

const data1 = fetch("./user.json")
const data2 = fetch("./user2.json")

Promise.all([data1, data2])
  .then(([res1, res2]) => {
    console.log("res1", res1);
    console.log("res2", res2);
  })
  .catch((err) => console.log(err))
  .finally(() => console.log("done!"));
promise all 【JS學習筆記】了解 Promise 的基本使用

Promise.race()

Promise.all() 一樣,Promise.race() 也是以 array 形式傳入多個 promise 函數,並在全部執行完畢後回傳第一個運行完成的結果。

const data1 = fetch("./user.json")
const data2 = fetch("./user2.json")

Promise.race([data1, data2])
  .then((res) => {
    console.log("res", res);
  })
  .catch((err) => console.log(err))
  .finally(() => console.log("done!"));
promise race 【JS學習筆記】了解 Promise 的基本使用

不過實際情況中 Promise.race() 使用的機會並不多。

補充:Promise.all 和 Promise.allSettled

Promise.allPromise.allSettled 兩個都是將 Promise 函數作為參數,區別在於使用 Promise.all 時,只要有其中一個 Promise 被拒絕就會立即停止;而 Promise.allSettled 則是會繼續執行到所有 Promise 都解決或拒絕。

Promise.all

假設 promise1 為 resolve,promise2 為 reject,使用 Promise.all 時遇到 reject 就會直接停止並調用 catch,必須所有 Promise 都為 resolve 才會進入 then

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));

Promise.all([promise1, promise2])
	.then((results) => results.forEach((result) => console.log(result.status)))
	.catch((e) => console.log(e)); // foo

Promise.allSettled

同樣的例子將 Promise.all 改為 Promise.allSettled,在遇到 reject 時不會直接停止而是繼續執行直到所有 Promise 執行完畢 :

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));

Promise.allSettled([promise1, promise2])
	.then((results) => results.forEach((result) => console.log(result.status))) 
	.catch((e) => console.log(e));
/*
  fulfilled
  rejected
*/

常見的錯誤

  • Uncaught TypeError: undefined is not a promise:請確保使用 new Promise() 而不是 Promise()
  • UnhandledPromiseRejectionWarning:調用的 Promise 被拒絕,但是沒有用於處理錯誤的 catch

Promise 的優勢

  1. 指定回調函數的方式更靈活且彈性。
  2. 支持鏈式調用,可以解決回呼地域(回呼函數嵌套調用)的問題。
    • 不過使用 asyncawait 會是更好的辦法。

參考資料

0 0 評分數
Article Rating
訂閱
通知
guest

0 Comments
在線反饋
查看所有評論