Stories

Detail Return Return

前端工程師復健筆記-JavaScript 核心深度複習-Promise及有關函數 - Stories Detail

Promise 詳解及常用方法對比

1. Promise 原理詳解

1.1 Promise 基本概念

Promise 是 JavaScript 中用於處理異步操作的對象,它代表一個異步操作的最終完成(或失敗)及其結果值。

// Promise 的三種狀態
const promise = new Promise((resolve, reject) => {
  // Pending 狀態(進行中)
  
  // 異步操作成功
  resolve(value); // Fulfilled 狀態(已成功)
  
  // 異步操作失敗
  reject(reason); // Rejected 狀態(已失敗)
});

1.2 Promise 狀態機制

  • Pending(等待):初始狀態
  • Fulfilled(已完成):操作成功完成
  • Rejected(已拒絕):操作失敗

狀態轉換特點

  • 狀態一旦改變就不可逆
  • 只能從 Pending → Fulfilled 或 Pending → Rejected

1.3 Promise 實現原理

class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      }

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onFulfilled(this.value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    });
  }
}

2. Promise 組合方法詳解

2.1 Promise.all

特點

  • 所有 Promise 都成功時返回結果數組
  • 任何一個 Promise 失敗立即拒絕
Promise.all = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Arguments must be an array'));
    }
    
    const results = [];
    let completedCount = 0;
    
    if (promises.length === 0) {
      return resolve(results);
    }
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => {
          results[index] = value;
          completedCount++;
          
          if (completedCount === promises.length) {
            resolve(results);
          }
        },
        reason => {
          reject(reason);
        }
      );
    });
  });
};

使用場景

// 場景1:並行請求多個接口,需要所有數據都獲取成功
const fetchUserData = fetch('/api/user');
const fetchProductData = fetch('/api/products');
const fetchOrderData = fetch('/api/orders');

Promise.all([fetchUserData, fetchProductData, fetchOrderData])
  .then(([user, products, orders]) => {
    console.log('所有數據加載完成');
    renderDashboard(user, products, orders);
  })
  .catch(error => {
    console.error('有一個請求失敗:', error);
    showError('數據加載失敗');
  });

// 場景2:並行執行多個計算任務
const calculations = [
  expensiveCalculation1(),
  expensiveCalculation2(),
  expensiveCalculation3()
];

Promise.all(calculations)
  .then(results => {
    const total = results.reduce((sum, num) => sum + num, 0);
    console.log('計算結果總和:', total);
  });

2.2 Promise.race

特點

  • 返回最先完成(成功或失敗)的 Promise 結果
Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Arguments must be an array'));
    }
    
    promises.forEach(promise => {
      Promise.resolve(promise).then(resolve, reject);
    });
  });
};

使用場景

// 場景1:請求超時控制
function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('請求超時')), timeout);
  });
  
  return Promise.race([fetchPromise, timeoutPromise]);
}

// 使用
fetchWithTimeout('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.message === '請求超時') {
      showTimeoutMessage();
    } else {
      showErrorMessage(error);
    }
  });

// 場景2:多個數據源競爭
const primaryDataSource = fetchFromPrimaryAPI();
const backupDataSource = fetchFromBackupAPI();

Promise.race([primaryDataSource, backupDataSource])
  .then(data => {
    console.log('使用最先返回的數據:', data);
    displayData(data);
  });

2.3 Promise.allSettled

特點

  • 等待所有 Promise 完成(無論成功或失敗)
  • 返回每個 Promise 的結果狀態描述對象
Promise.allSettled = function(promises) {
  return new Promise(resolve => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Arguments must be an array'));
    }
    
    const results = [];
    let completedCount = 0;
    
    if (promises.length === 0) {
      return resolve(results);
    }
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        value => {
          results[index] = { status: 'fulfilled', value };
          completedCount++;
          if (completedCount === promises.length) resolve(results);
        },
        reason => {
          results[index] = { status: 'rejected', reason };
          completedCount++;
          if (completedCount === promises.length) resolve(results);
        }
      );
    });
  });
};

使用場景

// 場景1:批量操作,需要知道每個操作的結果
const operations = [
  updateUserProfile(user1),
  updateUserProfile(user2),
  updateUserProfile(user3) // 這個可能會失敗
];

Promise.allSettled(operations)
  .then(results => {
    const successfulUpdates = results
      .filter(result => result.status === 'fulfilled')
      .map(result => result.value);
    
    const failedUpdates = results
      .filter(result => result.status === 'rejected')
      .map(result => result.reason);
    
    console.log(`成功更新: ${successfulUpdates.length} 個`);
    console.log(`失敗更新: ${failedUpdates.length} 個`);
    
    if (failedUpdates.length > 0) {
      sendErrorReport(failedUpdates);
    }
  });

// 場景2:多數據源採集,容忍部分失敗
const dataSources = [
  fetchFromSourceA(),
  fetchFromSourceB(),
  fetchFromSourceC(),
  fetchFromSourceD()
];

Promise.allSettled(dataSources)
  .then(results => {
    const validData = results
      .filter(result => result.status === 'fulfilled')
      .map(result => result.value);
    
    if (validData.length > 0) {
      processData(validData);
    } else {
      throw new Error('所有數據源都失敗了');
    }
  });

2.4 Promise.any

特點

  • 返回第一個成功的 Promise
  • 只有當所有 Promise 都失敗時才拒絕
Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Arguments must be an array'));
    }
    
    const errors = [];
    let rejectedCount = 0;
    
    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }
    
    promises.forEach((promise, index) => {
      Promise.resolve(promise).then(
        resolve,
        reason => {
          errors[index] = reason;
          rejectedCount++;
          
          if (rejectedCount === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

使用場景

// 場景1:多CDN資源加載,使用最先可用的
const cdnUrls = [
  'https://cdn1.example.com/library.js',
  'https://cdn2.example.com/library.js',
  'https://cdn3.example.com/library.js'
];

const loadScriptPromises = cdnUrls.map(url => 
  loadScript(url).catch(() => {
    // 忽略單個加載失敗,繼續嘗試其他CDN
    console.log(`${url} 加載失敗,嘗試下一個`);
    return Promise.reject();
  })
);

Promise.any(loadScriptPromises)
  .then(() => {
    console.log('腳本加載成功');
    initializeApp();
  })
  .catch(() => {
    console.error('所有CDN都不可用');
    showFallbackMessage();
  });

// 場景2:服務健康檢查
const healthChecks = [
  checkServiceHealth('primary'),
  checkServiceHealth('secondary'),
  checkServiceHealth('backup')
];

Promise.any(healthChecks)
  .then(healthyService => {
    console.log(`使用健康服務: ${healthyService.name}`);
    connectToService(healthyService);
  })
  .catch(() => {
    console.error('所有服務都不可用');
    enterMaintenanceMode();
  });

3. 方法對比總結

方法 成功條件 失敗條件 返回值 使用場景
Promise.all 所有成功 任一失敗 結果數組 並行操作,全部成功才繼續
Promise.race 第一個完成 第一個失敗 單個結果 超時控制、競速
Promise.allSettled 所有完成 不會失敗 狀態數組 需要知道每個操作結果
Promise.any 任一成功 全部失敗 成功結果 容錯、多備選方案

4. 實際應用示例

4.1 組合使用示例

// 複雜的異步操作流程
async function comprehensiveDataProcessing() {
  try {
    // 第一步:並行獲取基礎數據
    const [user, settings] = await Promise.all([
      fetchUserInfo(),
      fetchUserSettings()
    ]);
    
    // 第二步:競速獲取推薦內容(多個推薦源)
    const recommendation = await Promise.any([
      getRecommendationFromAI(),
      getRecommendationFromTrending(),
      getRecommendationFromHistory()
    ]);
    
    // 第三步:並行執行不相關的操作
    const [notifications, ads] = await Promise.allSettled([
      fetchNotifications(),
      fetchAdvertisements() // 廣告可能加載失敗,但不影響主流程
    ]);
    
    // 處理結果
    return {
      user,
      settings,
      recommendation,
      notifications: notifications.status === 'fulfilled' ? notifications.value : [],
      ads: ads.status === 'fulfilled' ? ads.value : null
    };
    
  } catch (error) {
    console.error('數據處理失敗:', error);
    throw error;
  }
}

4.2 錯誤處理最佳實踐

// 統一的錯誤處理策略
class PromiseManager {
  static async safeAll(promises, options = {}) {
    const { continueOnError = false, timeout } = options;
    
    if (timeout) {
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Operation timeout')), timeout);
      });
      promises = [Promise.all(promises), timeoutPromise];
    }
    
    try {
      if (continueOnError) {
        const results = await Promise.allSettled(promises);
        return this.processSettledResults(results);
      } else {
        return await Promise.all(promises);
      }
    } catch (error) {
      this.handleError(error);
      throw error;
    }
  }
  
  static processSettledResults(results) {
    return {
      successes: results
        .filter(r => r.status === 'fulfilled')
        .map(r => r.value),
      failures: results
        .filter(r => r.status === 'rejected')
        .map(r => r.reason)
    };
  }
  
  static handleError(error) {
    // 統一的錯誤處理邏輯
    console.error('Promise操作失敗:', error);
    // 可以在這裏添加錯誤上報等邏輯
  }
}

通過深入理解 Promise 的原理和各種組合方法的特點,可以在前端開發中更加優雅地處理複雜的異步操作場景,提高代碼的可讀性和可維護性。

user avatar toopoo Avatar dingtongya Avatar grewer Avatar alibabawenyujishu Avatar smalike Avatar nihaojob Avatar aqiongbei Avatar littlelyon Avatar leexiaohui1997 Avatar linx Avatar banana_god Avatar hard_heart_603dd717240e2 Avatar
Favorites 167 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.