iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0
JavaScript

30天 JavaScript 提升計畫:從零到精通結合2024年的創新功能系列 第 9

第 9 天:非同步程式設計 - Promises

  • 分享至 

  • xImage
  •  

Promises 可以幫助理解非同步操作的基本處理機制,這對於理解後續的 async/await 很重要,所以先從 promise 開始說起非同步。

同步 vs 非同步


特性 同步 非同步
執行方式 依次執行 允許其他任務在等待操作時執行
程式碼順序 由上而下依序編譯 可以在任務完成後透過回呼、Promiseasync/await 繼續執行
性能影響 可能導致長時間的阻塞,導致使用者過長時間等待畫面出現 提高反應性,避免長時間的阻塞
複雜性 通常較簡單,但是因為太簡單,不適合不可預測的任務 適合網路請求、計時器,不受時間阻塞的操作任務
常用方法/函數 Array.forEach()Math.max() setTimeout()fetch()Promisesasync/await
範例 synchronous asynchronous
結果 1->2->3 1->3->2

創建和使用 Promise


Promise 是 JavaScript 的內建對象,用來表示一個非同步操作的最終完成或失敗的結果值,主要有三種狀態:

Pending(待定):初始狀態,既不是成功也不是失敗。
Fulfilled(已兌現):操作成功完成。
Rejected(已拒絕):操作失敗。

創建 Promise
使用 Promise 建構函數可以建立一個新的 Promise 物件。
建構函式接受一個執行器函式作為參數,函式有兩個參數:resolvereject;當非同步操作成功時,呼叫 resolve;當非同步操作失敗時,呼叫 reject

const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    // 建構一個新的 Promise 函式
    setTimeout(() => {
      if (status) {
        resolve("promiseSetTimeout 成功");
      } else {
        reject("promiseSetTimeout 失敗");
      }
    }, 0);
  });
};
promiseSetTimeout(true).then(function (res) {
  console.log(res); // promiseSetTimeout 成功
});

🔔 程式碼補充說明:
呼叫 promiseSetTimeout 後把狀態參數帶入,然後運行 promise 過程,接著進入 pending 狀態,等到 pending 狀態結束才會往成功或失敗前進。

使用 Promise
可以使用 thencatch 方法來處理 Promise 的結果或錯誤。

then:用於處理 Promise 的成功結果。
catch:用來處理 Promise 的錯誤。

myPromise
  .then((result) => {
    console.log(result); // 成功
  })
  .catch((error) => {
    console.log(error);  // 失敗
  });

Promise chain 和 error handle


Promise chain
Promise chain 是一種使用 .then().catch() 方法依序處理非同步操作的技術。
Promise流程

API 取得資料後,可能取得第一個後才能取第二個接著第三個,這時候就可以使用 promise 的方法,這有助於避免回調地獄,即嵌套回調變得難以閱讀和管理的情況。

回調地獄

圖片來源:Node 7.6 + Koa 2: asynchronous flow control made right

以下是用 Promise chain 處理多個連續的非同步操作,並且每個 .then 中的處理函數都可以處理前一個 .then 的結果。

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("Data fetched"), 1000);
  });
};

const processData = (data) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`${data}and processed`), 1000);
  });
};

fetchData()
  .then((result) => {
    console.log(result);           // Data fetched
    return processData(result);
  })
  .then((processedResult) => {
    console.log(processedResult);  // Data fetched and processed
  })
  .catch((error) => {
    console.error("Error::", error);
  });

error handle
error handle flow
在 Promise 鏈中,錯誤處理可以透過 .catch() 捕捉鏈中任何 .then 方法拋出的錯誤,並且可以在鏈的最後進行統一處理。

const promiseSetTimeout = (status) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (status) {
        resolve("promiseSetTimeout 成功");
      } else {
        reject("promiseSetTimeout 失敗");
      }
    }, 500);
  });
};

promiseSetTimeout(true)
  .then((res) => {
    console.log(1, res);
    return promiseSetTimeout(false);
  })
  .then((res) => {
    console.log(2, res);
    return promiseSetTimeout(true);
  })
  .then((res) => {
    console.log(3, res);
  })
  .catch((err) => {
    console.log(err);
    return promiseSetTimeout(true);
  })
  .then((res) => {
    console.log(`我回來啦~`, res);
  });

依照以上程式碼回傳結果順序:1 'promiseSetTimeout 成功’ → promiseSetTimeout 失敗 → 我回來啦~ promiseSetTimeout 成功
因為,Promise 遇到錯誤的時候,就算中間有再多的串接,還是會找最近的 .catch 回傳,中間就直接跳過,如果後面又有串接就會執行。

構造函數方法


Promise.all() -> 實戰中使用率比較高,但也相對很雷!面試有被問過!
Promise.all() 會同時執行,並且在全部完成後統一回傳陣列,這個陣列的內容也是 promise 中 resolve 的內容。
不過,使用這個方法有個很大的致命傷:如果其中有事件 reject,那麼此 promise 也均視為失敗進入 .catch

const promise1 = Promise.resolve(8);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(resolve, 100, "kuku")
);
const promise3 = 666;

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values);                   // [8,"kuku",666]
  })
  .catch((error) => {
    console.error("Error:", error);
  });

Promise.race() -> 很少用到
使用 Promise.race 傳入多個 promise 事件,這個方法僅會回傳第一個完成的事件。

const promise1 = new Promise((resolve, reject) =>
  setTimeout(resolve, 500, "我還要化妝需要 500 ms!")
);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(resolve, 100, "我很快,叫我 100ms 先生!")
);

Promise.race([promise1, promise2])
  .then((value) => {
    console.log(value);                   // "我很快,叫我 100ms 先生!"
  })
  .catch((error) => {
    console.error("Error:", error);
  });

上一篇
第 8 天:模組化程式設計
下一篇
第 10 天:非同步程式設計 - async/await
系列文
30天 JavaScript 提升計畫:從零到精通結合2024年的創新功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言