魯斯前端布魯斯前端

文章中英模式

布魯斯前端JS面試題目 - 手寫 Promise.all 實作

學習如何手寫實作 JavaScript Promise.all 方法,掌握處理多個非同步任務的技巧,提升前端面試競爭力。

影片縮圖

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

Promise.all 方法概述

Promise.all 是 JavaScript 中處理多個 Promise 的重要方法,它接收一個 Promise 陣列(或可迭代物件),並返回一個新的 Promise,該 Promise 在所有輸入的 Promise 都成功解析時才會成功,或者在任一 Promise 被拒絕時立即拒絕。

Promise.all 的特性

  • 1. 並行執行:所有 Promise 同時開始執行
  • 2. 等待全部:等待所有 Promise 都完成
  • 3. 一個失敗則全失敗:任一 Promise 拒絕,整體立即拒絕
  • 4. 保持結果順序:結果陣列順序與輸入陣列順序一致
  • 5. 非 Promise 值:自動使用 Promise.resolve() 包裝

手寫實作 Promise.all 是前端面試中的熱門題目,它可以檢驗對 Promise、非同步處理和陣列操作的理解。

原生 Promise.all 使用方式

在實作之前,先來看看 Promise.all 的原生使用方式:

// 創建三個 Promise
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve) => setTimeout(() => resolve(2), 1000));
const promise3 = Promise.resolve(3);

// 使用 Promise.all 並行處理三個 Promise
Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // [1, 2, 3] (順序與輸入陣列相同)
  })
  .catch(error => {
    console.error('至少有一個 Promise 被拒絕', error);
  });

// 處理拒絕的例子
const promise4 = Promise.resolve(4);
const promise5 = Promise.reject('錯誤發生');
const promise6 = Promise.resolve(6);

Promise.all([promise4, promise5, promise6])
  .then(results => {
    console.log('不會執行到這裡');
  })
  .catch(error => {
    console.error(error); // '錯誤發生' (來自被拒絕的 promise5)
  });

手寫實作 Promise.all

現在讓我們來實作 Promise.all 方法:

// 基本实现 Promise.all
function promiseAll(promises) {
  // 检查输入是否为可迭代对象
  if (!promises[Symbol.iterator]) {
    return Promise.reject(new TypeError('参数必须是可迭代的'));
  }
  
  // 将输入转换为数组
  const promiseArray = Array.from(promises);
  const len = promiseArray.length;
  
  // 创建一个新的 Promise
  return new Promise((resolve, reject) => {
    // 空数组直接返回空结果
    if (len === 0) {
      return resolve([]);
    }
    
    // 存储结果的数组
    const results = new Array(len);
    // 完成的 Promise 计数
    let resolvedCount = 0;
    
    // 处理每个 Promise
    promiseArray.forEach((promise, index) => {
      // 将非 Promise 值包装为 Promise
      Promise.resolve(promise)
        .then(value => {
          // 按原顺序存储结果
          results[index] = value;
          resolvedCount++;
          
          // 当所有 Promise 都完成时,解析整体 Promise
          if (resolvedCount === len) {
            resolve(results);
          }
        })
        .catch(error => {
          // 任一 Promise 被拒绝,整体 Promise 立即拒绝
          reject(error);
        });
    });
  });
}

讓我們測試一下這個實作:

// 測試案例
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = 3; // 非 Promise 值,將被自動包裝

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

// 測試錯誤案例
const p4 = Promise.resolve(4);
const p5 = Promise.reject('失敗');
const p6 = Promise.resolve(6);

promiseAll([p4, p5, p6])
  .then(results => {
    console.log('不會執行到這裡');
  })
  .catch(error => {
    console.error(error); // '失敗'
  });