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 的原理和各種組合方法的特點,可以在前端開發中更加優雅地處理複雜的異步操作場景,提高代碼的可讀性和可維護性。