魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - Promise、Async/Await 深入解析

深入講解 JavaScript 中 Promise、Async/Await 的運作原理、使用方式、常見方法及實作,幫助你輕鬆應對面試中的相關問題。

影片縮圖

懶得看文章?那就來看影片吧

Promise 基本概念

Promise 是 JavaScript 中處理非同步操作的標準方式,代表一個尚未完成但未來會完成的操作。Promise 有三種狀態:

  • Pending(等待中):初始狀態,既不是成功也不是失敗
  • Fulfilled(已實現):操作成功完成
  • Rejected(已拒絕):操作失敗
const promise = new Promise((resolve, reject) => {
  // Asynchronous operation
  if (/* success */) {
    resolve(value);  // state changes to fulfilled
  } else {
    reject(error);   // state changes to rejected
  }
});

為何需要 Promise?

Promise 解決了傳統回調函式的多個問題,提供了更優雅的非同步處理方式:

  • 避免回調地獄:解決巢狀回調導致的程式碼難以維護問題
  • 統一錯誤處理:使用 catch 方法統一處理錯誤
  • 鏈式調用:通過 then 方法實現清晰的流程控制
  • 更好的語義化:Promise 的狀態轉換更符合程式設計直覺
// 傳統回調方式
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log(finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

// Promise 方式
doSomething()
  .then(result => doSomethingElse(result))
  .then(newResult => doThirdThing(newResult))
  .then(finalResult => console.log(finalResult))
  .catch(failureCallback);

Async/Await 的優勢

Async/Await 是建立在 Promise 之上的語法糖,提供了更直觀的方式來處理非同步操作:

  • 同步風格:使非同步程式碼看起來像同步程式碼
  • 錯誤處理:可以使用傳統的 try/catch 處理錯誤
  • 除錯方便:可以像同步程式碼一樣逐行除錯
  • 條件處理:更容易實現複雜的條件判斷邏輯
async function fetchUserData() {
  try {
    const response = await fetch('/api/user');
    const userData = await response.json();
    return userData;
  } catch (error) {
    console.error('Error fetching user data:', error);
    throw error;
  }
}

Promise 的靜態方法

Promise.all()

並行執行多個 Promise,等待全部完成。如果任一 Promise 失敗,整個操作就失敗。

Promise.all([
  fetch('/api/users'),
  fetch('/api/posts')
]).then(([users, posts]) => {
  // process results
});

Promise.race()

返回最先完成的 Promise 結果,無論成功或失敗。

Promise.race([
  fetch('/api/data'),
  new Promise((_, reject) =>
    setTimeout(() => reject('Timeout'), 5000)
  )
]);

Promise.any()

返回第一個成功的 Promise 結果,只有全部失敗時才會拒絕。

Promise.any([
  fetch('https://api1.example.com'),
  fetch('https://api2.example.com')
]).then(firstSuccess => {
  // Handle the first successful result
});

Promise.allSettled()

等待所有 Promise 完成,返回每個 Promise 的結果狀態。

Promise.allSettled([
  fetch('/api/users'),
  fetch('/api/posts')
]).then(results => {
  // process all results
});

🔥 常見面試題目

(一)Promise 的狀態轉換規則是什麼?

解答:Promise 的狀態轉換有以下特點:

  • 初始狀態為 Pending(等待中)
  • 只能從 Pending 轉換到 Fulfilled(已實現)或 Rejected(已拒絕)
  • 狀態轉換是不可逆的,一旦改變就不能再變化
  • 狀態轉換時會觸發對應的回調函式(then 或 catch)

(二)Promise.all 和 Promise.race 的區別?

解答:

特性Promise.allPromise.race
完成條件所有 Promise 都完成任一 Promise 完成
失敗處理任一失敗則整體失敗採用最先完成的結果
返回值所有結果的陣列最先完成的結果

(三)async/await 和 Promise 的關係?

解答:

  • async/await 是建立在 Promise 之上的語法糖
  • async 函式總是返回一個 Promise
  • await 只能在 async 函式內使用
  • await 可以等待任何 "thenable" 物件(實現了 then 方法的物件)
// async 函式總是返回 Promise
async function example() {
  return "Hello";
}

// 等同於
function example() {
  return Promise.resolve("Hello");
}

// 驗證 async 返回的是 Promise
console.log(example()); // Promise {<fulfilled>: "Hello"}

// await 解開的是 Promise resolve 的值
async function example() {
  const result = await example(); // 等待 Promise.resolve("Hello") 的結果
  console.log(result); // "Hello"
  
  // 等同於
  example().then(value => console.log(value)); // "Hello"
}

// 完整範例比較
// Promise 方式
function fetchData() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => data)
    .catch(error => console.error(error));
}

// async/await 方式 - 更易讀的同步風格
async function fetchData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    return data; // 自動被包裝為 Promise.resolve(data)
  } catch (error) {
    console.error(error);
  }
}

(四)如何實現自己的 Promise.all?

解答:我們可以實現一個簡單的 MyPromise.all 函數,模擬 Promise.all 的功能:

function myPromiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 如果傳入的不是陣列,直接拒絕
    if (!Array.isArray(promises)) {
      return reject(new TypeError('promises must be an array'));
    }
    
    const results = [];  // 存儲所有 promise 的結果
    let completed = 0;   // 已完成的 promise 數量
    
    // 如果是空陣列,直接返回空陣列結果
    if (promises.length === 0) {
      return resolve(results);
    }
    
    // 遍歷所有 promise
    promises.forEach((promise, index) => {
      // 使用 Promise.resolve 確保處理非 Promise 值
      Promise.resolve(promise)
        .then(result => {
          results[index] = result;  // 保持原始順序
          completed++;
          
          // 當所有 promise 都完成時,解析最終結果
          if (completed === promises.length) {
            resolve(results);
          }
        })
        .catch(error => {
          // 任何一個 promise 拒絕,整個 myPromiseAll 就拒絕
          reject(error);
        });
    });
  });
}

// 使用範例
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = Promise.resolve(3);

myPromiseAll([p1, p2, p3])
  .then(results => console.log(results))  // [1, 2, 3]
  .catch(error => console.error(error));

這個實現的關鍵點:

  • 返回一個新的 Promise
  • 追蹤所有 Promise 的完成狀態
  • 保持結果的原始順序
  • 任一 Promise 失敗時立即拒絕整個 Promise
  • 使用 Promise.resolve 處理非 Promise 值