iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
JavaScript

React 觀念架構:從js 基礎到Hook 底層邏輯 系列 第 6

Day 06 Promise 與 Async/Await:處理非同步資料流的底層觀念

  • 分享至 

  • xImage
  •  

我們已經學習了陣列高階函數的轉換技巧。今天我們要進入前端開發中最關鍵的課題之一:非同步 (Asynchronous) 資料處理。

不論是呼叫 API 撈取資料、讀取檔案還是設定定時器,React 的資料處理(特別是在 useEffect 內部)高度依賴 JavaScript 的 Promise 與 Async/Await 機制。

一、 為什麼需要非同步?從 Callback Hell 到 Promise

JavaScript 是單線程 (Single-threaded) 的語言,一次只能做一件事。若發送網路請求時讓主執行緒停下來等待 API 回傳,整個瀏覽器畫面就會陷入凍結(畫面卡死)。

1. 傳統 Callback 的痛苦:Callback Hell

在 Promise 誕生前,非同步高度依賴 Callback Function。當遇到連續的 API 呼叫時,程式碼會演變成維護噩夢:

//  巢狀 Callback Hell
fetchUser(userId, (user) => {
  fetchPosts(user.id, (posts) => {
    fetchComments(posts[0].id, (comments) => {
      // 邏輯層層嵌套,錯誤處理極度困難
    });
  });
});

2. Promise 的誕生:優雅的狀態機

ES6 引入了 Promise,它代表一個未來才會得知結果的非同步操作。Promise 內部擁有三個互斥的狀態:

  • Pending(待定):初始狀態,操作進行中。

  • Fulfilled(已實現):操作成功,呼叫 resolve(data)。

  • Rejected(已拒絕):操作失敗,呼叫 reject(error)。

// 使用 Promise 進行鏈式呼叫 (Chaining)
fetchUser(userId)
  .then((user) => fetchPosts(user.id))
  .then((posts) => fetchComments(posts[0].id))
  .catch((error) => console.error('出錯了:', error));

二、 Async / Await:非同步程式碼的同步寫法

ES8 (ES2017) 推出的 async/await 是 Promise 的語法糖 (Syntactic Sugar),讓你能用看起來像「同步 (Synchronous)」的直覺順序寫非同步程式碼。

核心規則:
async 宣告在函數前,該函數永遠回傳一個 Promise。

await 只能在 async 函數內部使用,它會暫停該函數的執行,直到 Promise 狀態變為 Fulfilled 或 Rejected。

async function getUserData(userId) {
  try {
    const user = await fetchUser(userId);
    const posts = await fetchPosts(user.id);
    return posts;
  } catch (error) {
    // 使用傳統 try...catch 即可優雅捕捉非同步錯誤
    console.error('API 請求失敗:', error);
  }
}

三、 React 中的實務應用與踩坑實錄

在 React 中,我們最常在 useEffect 中呼叫非同步 API,但這裡藏著兩個大坑:

1. 不能直接把 useEffect 的 callback 變成 async

//  錯誤寫法:useEffect 不能傳入 async 函數!
useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

原因:useEffect 規範 callback 必須回傳 undefined 或 Cleanup Function (清除函數)。但 async 函數永遠會回傳一個 Promise,這會破壞 React 的 Cleanup 機制。

正確做法:在內部宣告獨立的 async 函數並呼叫它

useEffect(() => {
  // 在內部定義 async 函數
  const loadData = async () => {
    try {
      const data = await fetchData();
      setData(data);
    } catch (err) {
      setError(err.message);
    }
  };

  loadData(); // 執行它
}, []);

2. 效能優化:平行請求 (Promise.all)

若有多個互相獨立的 API 請求,切勿串行( sequential)使用 await,否則會造成不必要的等待累積:

//  慢:串行等待(總耗時 = 2秒 + 3秒 = 5秒)
const user = await fetchUser();   // 耗時 2 秒
const config = await fetchConfig(); // 耗時 3 秒

//  快:平行發送(總耗時 = 取最大值 3秒)
const [user, config] = await Promise.all([
  fetchUser(),
  fetchConfig()
]);

上一篇
DAY 5 Array 高階函數 (map, filter, reduce) 在 JSX 與資料處理中的應用
下一篇
Day 07 Event Loop 機制:微任務 (Microtask) 與 React 批次更新
系列文
React 觀念架構:從js 基礎到Hook 底層邏輯 7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言