Promise
Promise 是一种用于延迟计算的策略,适用于多种并发风格:用于本地计算的线程和事件循环并发,以及同步和异步远程消息传递。Promise 代表一个异步操作的最终结果。使用 Promises 的主要方式是通过一个方法,该方法注册从 promise 的最终值或失败原因到新 promise 的转换。

查看更多相关内容
async/await 是如何工作的?与 Promise 有什么关系?async/await 是 ES2017 引入的语法糖,用于处理异步操作,它基于 Promise 构建,让异步代码看起来更像同步代码,大大提高了代码的可读性和可维护性。
## async 函数
### 基本概念
async 函数是使用 `async` 关键字声明的函数,它总是返回一个 Promise。即使函数内部没有显式返回 Promise,也会被包装成一个 Promise。
### 基本用法
```javascript
async function fetchData() {
return 'Hello World';
}
// 等同于
function fetchData() {
return Promise.resolve('Hello World');
}
fetchData().then(result => console.log(result)); // 输出: Hello World
```
### 返回 Promise
```javascript
async function fetchData() {
// 返回普通值
return 42;
}
async function fetchDataWithError() {
// 抛出错误
throw new Error('出错了');
}
fetchData().then(result => console.log(result)); // 输出: 42
fetchDataWithError().catch(error => console.error(error.message)); // 输出: 出错了
```
## await 表达式
### 基本概念
await 关键字只能在 async 函数内部使用,它会暂停 async 函数的执行,等待 Promise 完成,然后返回 Promise 的结果。
### 基本用法
```javascript
async function fetchData() {
const promise = Promise.resolve('Hello');
const result = await promise;
console.log(result); // 输出: Hello
return result;
}
fetchData();
```
### 等待多个 Promise
```javascript
async function fetchMultipleData() {
const promise1 = fetch('/api/user');
const promise2 = fetch('/api/posts');
const [userResponse, postsResponse] = await Promise.all([promise1, promise2]);
const user = await userResponse.json();
const posts = await postsResponse.json();
return { user, posts };
}
```
## 错误处理
### 使用 try/catch
```javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error.message);
throw error; // 可以选择重新抛出错误
}
}
```
### 捕获特定错误
```javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError') {
console.error('网络连接问题');
} else if (error.message.includes('HTTP error')) {
console.error('服务器错误');
} else {
console.error('未知错误:', error);
}
throw error;
}
}
```
## async/await vs Promise.then()
### Promise.then() 链式调用
```javascript
function fetchData() {
return fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => ({ user, posts }))
.catch(error => {
console.error(error);
throw error;
});
}
```
### async/await(更易读)
```javascript
async function fetchData() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error(error);
throw error;
}
}
```
## 并行执行
### 使用 Promise.all
```javascript
async function fetchAllData() {
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { user, posts, comments };
}
```
### 使用 Promise.allSettled
```javascript
async function fetchAllDataWithErrors() {
const results = await Promise.allSettled([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`请求 ${index} 成功:`, result.value);
} else {
console.error(`请求 ${index} 失败:`, result.reason);
}
});
return results;
}
```
## 常见使用场景
### 1. 顺序执行异步操作
```javascript
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
```
### 2. 并行执行异步操作
```javascript
async function processItemsParallel(items) {
const promises = items.map(item => processItem(item));
const results = await Promise.all(promises);
return results;
}
```
### 3. 带超时的请求
```javascript
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
}
}
```
### 4. 重试机制
```javascript
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`尝试 ${i + 1} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
```
## 最佳实践
### 1. 总是使用 try/catch
```javascript
// 不推荐:没有错误处理
async function fetchData() {
const response = await fetch('/api/data');
return await response.json();
}
// 推荐:添加错误处理
async function fetchData() {
try {
const response = await fetch('/api/data');
return await response.json();
} catch (error) {
console.error('请求失败:', error);
throw error;
}
}
```
### 2. 避免在循环中顺序 await
```javascript
// 不推荐:顺序执行,速度慢
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// 推荐:并行执行,速度快
async function processItems(items) {
const promises = items.map(item => processItem(item));
return await Promise.all(promises);
}
```
### 3. 合理使用 Promise.all
```javascript
// 推荐:并行执行独立的异步操作
async function fetchData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return { user, posts, comments };
}
```
### 4. 使用 finally 进行清理
```javascript
async function fetchData() {
let connection;
try {
connection = await createConnection();
const data = await connection.query('SELECT * FROM users');
return data;
} catch (error) {
console.error('查询失败:', error);
throw error;
} finally {
if (connection) {
await connection.close();
}
}
}
```
## 常见陷阱
### 1. 忘记使用 await
```javascript
// 错误:没有 await
async function fetchData() {
const promise = fetch('/api/data');
// promise 是一个 Promise 对象,不是数据
console.log(promise); // 输出: Promise {<pending>}
}
// 正确:使用 await
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
```
### 2. 在非 async 函数中使用 await
```javascript
// 错误:在非 async 函数中使用 await
function fetchData() {
const data = await fetch('/api/data'); // SyntaxError
}
// 正确:在 async 函数中使用 await
async function fetchData() {
const data = await fetch('/api/data');
return data;
}
```
### 3. 过度使用 try/catch
```javascript
// 不推荐:过度使用 try/catch
async function fetchData() {
try {
try {
const response = await fetch('/api/data');
try {
const data = await response.json();
return data;
} catch (error) {
console.error('解析失败:', error);
}
} catch (error) {
console.error('请求失败:', error);
}
} catch (error) {
console.error('未知错误:', error);
}
}
// 推荐:合理使用 try/catch
async function fetchData() {
try {
const response = await fetch('/api/data');
return await response.json();
} catch (error) {
console.error('请求或解析失败:', error);
throw error;
}
}
```
## 与 Promise 的关系
async/await 本质上是 Promise 的语法糖,它们之间可以互相转换:
```javascript
// async/await
async function fetchData() {
const response = await fetch('/api/data');
return await response.json();
}
// 等同于 Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json());
}
```
## 总结
1. **async 函数总是返回 Promise**:即使返回普通值也会被包装成 Promise
2. **await 暂停执行**:等待 Promise 完成后继续执行
3. **使用 try/catch 处理错误**:确保错误被正确捕获和处理
4. **并行执行提高性能**:使用 Promise.all 并行执行独立的异步操作
5. **避免过度嵌套**:保持代码扁平和清晰
6. **理解与 Promise 的关系**:async/await 是 Promise 的语法糖,可以互相转换
前端 · 2月22日 14:31
什么是 Promise?Promise 有哪些状态?Promise 是 JavaScript 中处理异步操作的重要机制,它代表一个异步操作的最终完成或失败。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。一旦状态改变,就不会再变。
## 核心概念
### Promise 的三种状态
- **pending**: 初始状态,既不是成功也不是失败
- **fulfilled**: 操作成功完成
- **rejected**: 操作失败
### 基本用法
```javascript
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
if (/* 操作成功 */) {
resolve('成功的结果');
} else {
reject('失败的原因');
}
}, 1000);
});
promise.then(result => {
console.log(result); // 处理成功
}).catch(error => {
console.log(error); // 处理失败
});
```
### Promise 链式调用
```javascript
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
```
### Promise 静态方法
**Promise.all()**: 所有 Promise 都成功才返回成功,有一个失败就返回失败
```javascript
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(error => console.error(error));
```
**Promise.race()**: 返回最先完成的结果(无论成功或失败)
```javascript
Promise.race([promise1, promise2])
.then(result => console.log(result));
```
**Promise.allSettled()**: 等待所有 Promise 完成,返回每个 Promise 的状态
```javascript
Promise.allSettled([promise1, promise2])
.then(results => console.log(results));
```
**Promise.any()**: 返回第一个成功的 Promise
```javascript
Promise.any([promise1, promise2])
.then(result => console.log(result));
```
## 常见面试问题
### 1. Promise 和回调函数的区别
- Promise 解决了回调地狱问题
- Promise 提供了更好的错误处理机制
- Promise 支持链式调用,代码更清晰
- Promise 状态不可逆,更符合直觉
### 2. 如何处理 Promise 错误
使用 `.catch()` 方法捕获错误,或者在 `.then()` 的第二个参数中处理:
```javascript
promise.then(
result => console.log(result),
error => console.error(error)
);
// 或者
promise.then(result => console.log(result))
.catch(error => console.error(error));
```
### 3. Promise 的微任务机制
Promise 的回调属于微任务,会在当前宏任务执行完后立即执行,优先级高于宏任务(如 setTimeout)。
### 4. async/await 与 Promise 的关系
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码:
```javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
```
## 最佳实践
1. **总是处理错误**: 使用 `.catch()` 或 try/catch
2. **避免嵌套 Promise**: 使用链式调用
3. **合理使用 Promise 静态方法**: 根据场景选择合适的方法
4. **理解事件循环**: 掌握微任务和宏任务的执行顺序
5. **性能优化**: 避免不必要的 Promise 包装
前端 · 2月22日 14:08
如何实现 Promise 的取消?Promise 的取消是一个常见但复杂的问题。Promise 本身不支持取消,但我们可以通过一些技巧来实现类似的功能。
## 为什么 Promise 不能被取消?
Promise 的设计遵循"不可逆"原则:
- 一旦 Promise 状态改变(pending → fulfilled 或 pending → rejected),就不能再改变
- 这种设计保证了 Promise 的可靠性和可预测性
- 取消操作会引入额外的复杂性和不确定性
## 实现取消的几种方法
### 1. 使用 AbortController
AbortController 是现代浏览器提供的取消异步操作的标准 API。
#### 基本用法
```javascript
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('请求失败:', error);
}
});
// 取消请求
controller.abort();
```
#### 封装可取消的 fetch
```javascript
function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
return fetch(url, { ...options, signal: controller.signal })
.then(response => {
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch(error => {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时');
}
throw error;
});
}
// 使用示例
fetchWithTimeout('/api/data', {}, 3000)
.then(data => console.log(data))
.catch(error => console.error(error));
```
### 2. 使用包装函数
通过包装函数来实现取消功能。
#### 基本实现
```javascript
function makeCancellable(promise) {
let hasCancelled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => {
if (!hasCancelled) resolve(value);
},
error => {
if (!hasCancelled) reject(error);
}
);
});
return {
promise: wrappedPromise,
cancel: () => {
hasCancelled = true;
}
};
}
// 使用示例
const { promise, cancel } = makeCancellable(
fetch('/api/data').then(r => r.json())
);
promise
.then(data => console.log(data))
.catch(error => {
if (error.name === 'CancellationError') {
console.log('操作被取消');
} else {
console.error('操作失败:', error);
}
});
// 取消操作
cancel();
```
#### 完整实现
```javascript
class CancellablePromise {
constructor(executor) {
this.isCancelled = false;
this.rejectors = [];
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
executor(
value => {
if (!this.isCancelled) {
resolve(value);
}
},
error => {
if (!this.isCancelled) {
reject(error);
}
}
);
});
}
cancel() {
this.isCancelled = true;
this.rejectors.forEach(rejector => {
rejector(new Error('Promise cancelled'));
});
}
then(onFulfilled, onRejected) {
const promise = this.promise.then(onFulfilled, onRejected);
return new CancellablePromise((resolve, reject) => {
this.rejectors.push(reject);
promise.then(resolve, reject);
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
value => {
onFinally();
return value;
},
error => {
onFinally();
throw error;
}
);
}
}
// 使用示例
const cancellablePromise = new CancellablePromise((resolve) => {
setTimeout(() => {
resolve('完成');
}, 2000);
});
cancellablePromise
.then(result => console.log(result))
.catch(error => console.error(error));
// 取消
setTimeout(() => {
cancellablePromise.cancel();
}, 1000);
```
### 3. 使用令牌(Token)模式
通过传递令牌来检查是否应该继续执行。
```javascript
class CancellationToken {
constructor() {
this.isCancelled = false;
}
cancel() {
this.isCancelled = true;
}
throwIfCancelled() {
if (this.isCancelled) {
throw new Error('Operation cancelled');
}
}
}
function fetchWithToken(url, token) {
return fetch(url)
.then(response => {
token.throwIfCancelled();
return response.json();
})
.then(data => {
token.throwIfCancelled();
return data;
});
}
// 使用示例
const token = new CancellationToken();
fetchWithToken('/api/data', token)
.then(data => console.log(data))
.catch(error => {
if (error.message === 'Operation cancelled') {
console.log('操作被取消');
} else {
console.error('操作失败:', error);
}
});
// 取消操作
setTimeout(() => {
token.cancel();
}, 1000);
```
### 4. 使用 Promise.race 实现超时
```javascript
function promiseWithTimeout(promise, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Timeout'));
}, timeout);
});
return Promise.race([promise, timeoutPromise]);
}
// 使用示例
promiseWithTimeout(
fetch('/api/data').then(r => r.json()),
3000
)
.then(data => console.log(data))
.catch(error => {
if (error.message === 'Timeout') {
console.log('请求超时');
} else {
console.error('请求失败:', error);
}
});
```
## 实际应用场景
### 1. 取消重复的搜索请求
```javascript
class SearchService {
constructor() {
this.currentController = null;
}
async search(query) {
// 取消之前的请求
if (this.currentController) {
this.currentController.abort();
}
this.currentController = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: this.currentController.signal
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('搜索请求被取消');
return null;
}
throw error;
}
}
}
// 使用示例
const searchService = new SearchService();
// 用户快速输入,只保留最后一次搜索
searchService.search('hello');
searchService.search('hello world');
searchService.search('hello world example');
```
### 2. 取消长时间运行的任务
```javascript
class TaskManager {
constructor() {
this.tasks = new Map();
}
async runTask(taskId, task) {
const controller = new AbortController();
this.tasks.set(taskId, controller);
try {
const result = await task(controller.signal);
return result;
} catch (error) {
if (error.name === 'AbortError') {
console.log(`任务 ${taskId} 被取消`);
return null;
}
throw error;
} finally {
this.tasks.delete(taskId);
}
}
cancelTask(taskId) {
const controller = this.tasks.get(taskId);
if (controller) {
controller.abort();
}
}
}
// 使用示例
const taskManager = new TaskManager();
// 运行任务
taskManager.runTask('task1', async (signal) => {
for (let i = 0; i < 10; i++) {
signal.throwIfAborted();
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(`步骤 ${i + 1} 完成`);
}
return '任务完成';
});
// 取消任务
setTimeout(() => {
taskManager.cancelTask('task1');
}, 3000);
```
### 3. 组件卸载时取消请求
```javascript
class Component {
constructor() {
this.controller = new AbortController();
}
async fetchData() {
try {
const response = await fetch('/api/data', {
signal: this.controller.signal
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('组件卸载,请求被取消');
return null;
}
throw error;
}
}
destroy() {
this.controller.abort();
}
}
// 使用示例
const component = new Component();
component.fetchData().then(data => {
if (data) {
console.log('数据加载成功:', data);
}
});
// 组件卸载时
setTimeout(() => {
component.destroy();
}, 1000);
```
## 最佳实践
### 1. 总是清理资源
```javascript
async function fetchData() {
const controller = new AbortController();
try {
const response = await fetch('/api/data', {
signal: controller.signal
});
return await response.json();
} finally {
controller.abort();
}
}
```
### 2. 提供取消回调
```javascript
function fetchWithCancel(url, onCancel) {
const controller = new AbortController();
const promise = fetch(url, { signal: controller.signal })
.then(response => response.json());
promise.cancel = () => {
controller.abort();
if (onCancel) {
onCancel();
}
};
return promise;
}
// 使用示例
const promise = fetchWithCancel('/api/data', () => {
console.log('请求被取消');
});
promise.then(data => console.log(data));
// 取消请求
promise.cancel();
```
### 3. 处理取消错误
```javascript
async function fetchWithCancellation(url) {
const controller = new AbortController();
try {
const response = await fetch(url, {
signal: controller.signal
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被取消');
return null;
}
throw error;
}
}
```
## 总结
1. **Promise 本身不支持取消**:需要通过其他方式实现
2. **AbortController 是标准方案**:现代浏览器推荐使用
3. **包装函数提供灵活性**:可以根据需求定制取消逻辑
4. **令牌模式适合复杂场景**:可以精细控制取消时机
5. **总是清理资源**:避免内存泄漏
6. **处理取消错误**:区分取消错误和其他错误
7. **提供取消回调**:让调用者知道操作被取消
8. **考虑用户体验**:取消操作应该快速响应
前端 · 2月22日 14:07
Promise 的常见陷阱和最佳实践有哪些?Promise 的常见陷阱和最佳实践是每个 JavaScript 开发者都应该掌握的知识。了解这些陷阱可以帮助我们写出更健壮、更高效的异步代码。
## 常见陷阱
### 1. 忘记返回 Promise
**问题示例:**
```javascript
// 不推荐:忘记返回 Promise
function fetchData() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
// 忘记返回数据
});
}
// 调用者无法获取数据
fetchData().then(data => {
console.log(data); // undefined
});
```
**正确做法:**
```javascript
// 推荐:返回 Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log(data);
return data; // 返回数据
});
}
// 调用者可以获取数据
fetchData().then(data => {
console.log(data); // 实际数据
});
```
### 2. 在 then 中嵌套 Promise
**问题示例:**
```javascript
// 不推荐:嵌套 Promise
fetch('/api/user')
.then(response => response.json())
.then(user => {
fetch(`/api/posts/${user.id}`)
.then(response => response.json())
.then(posts => {
console.log(posts);
});
});
```
**正确做法:**
```javascript
// 推荐:扁平链式调用
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts));
```
### 3. 忘记处理错误
**问题示例:**
```javascript
// 不推荐:没有错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// 如果请求失败,错误会被忽略
```
**正确做法:**
```javascript
// 推荐:添加错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
```
### 4. 在循环中顺序 await
**问题示例:**
```javascript
// 不推荐:顺序执行,速度慢
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
```
**正确做法:**
```javascript
// 推荐:并行执行,速度快
async function processItems(items) {
const promises = items.map(item => processItem(item));
return await Promise.all(promises);
}
```
### 5. 混用 async/await 和 Promise.then()
**问题示例:**
```javascript
// 不推荐:混用导致代码混乱
async function fetchData() {
const response = await fetch('/api/data');
return response.json().then(data => {
console.log(data);
return data;
});
}
```
**正确做法:**
```javascript
// 推荐:统一使用 async/await
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
return data;
}
```
### 6. 不必要的 Promise 包装
**问题示例:**
```javascript
// 不推荐:不必要的 Promise 包装
function fetchData() {
return new Promise((resolve) => {
fetch('/api/data')
.then(response => response.json())
.then(data => resolve(data));
});
}
```
**正确做法:**
```javascript
// 推荐:直接返回 Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json());
}
```
### 7. 在构造函数中执行异步操作
**问题示例:**
```javascript
// 不推荐:在构造函数中执行异步操作
class User {
constructor(id) {
this.id = id;
this.data = null;
fetch(`/api/users/${id}`)
.then(response => response.json())
.then(data => {
this.data = data;
});
}
getData() {
return this.data; // 可能返回 null
}
}
```
**正确做法:**
```javascript
// 推荐:使用静态工厂方法或初始化方法
class User {
constructor(id, data) {
this.id = id;
this.data = data;
}
static async create(id) {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return new User(id, data);
}
getData() {
return this.data; // 保证有数据
}
}
// 使用
const user = await User.create(1);
console.log(user.getData());
```
### 8. 过度使用 Promise.all
**问题示例:**
```javascript
// 不推荐:对不相关的操作使用 Promise.all
async function fetchData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
// 如果只需要用户数据,其他请求是浪费的
return user;
}
```
**正确做法:**
```javascript
// 推荐:只请求需要的数据
async function fetchData() {
const user = await fetchUser();
return user;
}
// 或者:按需加载
async function fetchAllData() {
const user = await fetchUser();
// 只在需要时加载其他数据
if (user.hasPosts) {
const posts = await fetchPosts();
return { user, posts };
}
return { user };
}
```
## 最佳实践
### 1. 总是返回 Promise
```javascript
// 推荐:函数总是返回 Promise
function fetchData() {
return fetch('/api/data')
.then(response => response.json());
}
// 调用者可以链式调用
fetchData()
.then(data => processData(data))
.then(result => console.log(result));
```
### 2. 使用 async/await 提高可读性
```javascript
// 推荐:使用 async/await
async function fetchUserData() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
return { user, posts };
} catch (error) {
console.error('获取数据失败:', error);
throw error;
}
}
```
### 3. 合理使用 Promise.all
```javascript
// 推荐:对独立的异步操作使用 Promise.all
async function fetchAllData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return { user, posts, comments };
}
```
### 4. 使用 Promise.allSettled 处理部分失败
```javascript
// 推荐:使用 Promise.allSettled
async function fetchMultipleUrls(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url))
);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failed = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
return { successful, failed };
}
```
### 5. 使用 Promise.race 实现超时
```javascript
// 推荐:使用 Promise.race 实现超时
function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), timeout);
});
return Promise.race([
fetch(url),
timeoutPromise
]);
}
```
### 6. 使用 Promise.any 获取第一个成功的结果
```javascript
// 推荐:使用 Promise.any
async function fetchFromMultipleSources(sources) {
try {
const response = await Promise.any(
sources.map(source => fetch(source.url))
);
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有数据源都失败了');
throw new Error('无法获取数据');
}
throw error;
}
}
```
### 7. 使用 finally 进行清理
```javascript
// 推荐:使用 finally
async function fetchDataWithCleanup() {
let connection;
try {
connection = await createConnection();
const data = await connection.query('SELECT * FROM users');
return data;
} catch (error) {
console.error('查询失败:', error);
throw error;
} finally {
if (connection) {
await connection.close();
}
}
}
```
### 8. 避免在循环中创建 Promise
```问题示例:**
```javascript
// 不推荐:在循环中创建 Promise
function processItems(items) {
const results = [];
for (const item of items) {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(processItem(item));
}, 1000);
});
results.push(promise);
}
return Promise.all(results);
}
```
**正确做法:**
```javascript
// 推荐:使用 map 创建 Promise
function processItems(items) {
const promises = items.map(item =>
new Promise((resolve) => {
setTimeout(() => {
resolve(processItem(item));
}, 1000);
})
);
return Promise.all(promises);
}
```
### 9. 使用 AbortController 取消请求
```javascript
// 推荐:使用 AbortController
class DataFetcher {
constructor() {
this.controller = new AbortController();
}
async fetch(url) {
try {
const response = await fetch(url, {
signal: this.controller.signal
});
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被取消');
return null;
}
throw error;
}
}
cancel() {
this.controller.abort();
}
}
```
### 10. 实现重试机制
```javascript
// 推荐:实现重试机制
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`尝试 ${i + 1} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error;
}
await new Promise(resolve =>
setTimeout(resolve, 1000 * (i + 1))
);
}
}
}
```
## 性能优化
### 1. 避免不必要的 await
```javascript
// 不推荐:不必要的 await
async function fetchData() {
const data1 = await fetch('/api/data1');
const data2 = await fetch('/api/data2');
const data3 = await fetch('/api/data3');
return { data1, data2, data3 };
}
// 推荐:并行执行
async function fetchData() {
const [data1, data2, data3] = await Promise.all([
fetch('/api/data1'),
fetch('/api/data2'),
fetch('/api/data3')
]);
return { data1, data2, data3 };
}
```
### 2. 使用缓存
```javascript
// 推荐:使用缓存
const cache = new Map();
async function fetchWithCache(url) {
if (cache.has(url)) {
return cache.get(url);
}
const data = await fetch(url).then(r => r.json());
cache.set(url, data);
return data;
}
```
### 3. 实现请求去重
```javascript
// 推荐:实现请求去重
const pendingRequests = new Map();
async function fetchDeduplicated(url) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = fetch(url)
.then(response => response.json())
.finally(() => {
pendingRequests.delete(url);
});
pendingRequests.set(url, promise);
return promise;
}
```
## 总结
1. **总是返回 Promise**:让调用者可以链式调用
2. **使用 async/await**:提高代码可读性
3. **避免嵌套 Promise**:保持代码扁平
4. **处理错误**:总是添加错误处理
5. **合理使用 Promise.all**:对独立的异步操作使用
6. **使用 Promise.allSettled**:处理部分失败的场景
7. **使用 Promise.race**:实现超时和竞争
8. **使用 Promise.any**:获取第一个成功的结果
9. **使用 finally**:进行清理工作
10. **实现重试机制**:提高可靠性
11. **使用 AbortController**:取消请求
12. **避免不必要的 await**:并行执行独立的异步操作
13. **使用缓存**:减少重复请求
14. **实现请求去重**:避免同时发起相同的请求
前端 · 2月22日 14:07
如何实现 Promise 的并发控制?Promise 的并发控制是一个重要的性能优化技术,它允许我们限制同时执行的异步操作数量,避免资源耗尽或性能下降。
## 为什么需要并发控制
### 问题场景
```javascript
// 不推荐:同时发起大量请求
async function fetchAllUrls(urls) {
const promises = urls.map(url => fetch(url));
const results = await Promise.all(promises);
return results;
}
// 问题:
// 1. 可能导致浏览器或服务器资源耗尽
// 2. 网络带宽可能被占满
// 3. 可能触发服务器的限流机制
// 4. 内存占用过高
```
## 基本并发控制实现
### 1. 使用 p-limit 库
```javascript
import pLimit from 'p-limit';
async function fetchWithLimit(urls, concurrency = 5) {
const limit = pLimit(concurrency);
const promises = urls.map(url =>
limit(() => fetch(url))
);
const results = await Promise.all(promises);
return results;
}
```
### 2. 手动实现并发控制
```javascript
async function asyncPool(poolLimit, array, iteratorFn) {
const ret = [];
const executing = [];
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}
// 使用示例
async function fetchWithConcurrency(urls, concurrency = 5) {
return asyncPool(concurrency, urls, url => fetch(url));
}
```
### 3. 使用队列实现
```javascript
class ConcurrencyControl {
constructor(concurrency) {
this.concurrency = concurrency;
this.queue = [];
this.running = 0;
}
async run(task) {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
return await task();
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// 使用示例
async function fetchWithControl(urls, concurrency = 5) {
const control = new ConcurrencyControl(concurrency);
const promises = urls.map(url =>
control.run(() => fetch(url))
);
return Promise.all(promises);
}
```
## 高级并发控制实现
### 1. 带重试机制的并发控制
```javascript
class ConcurrencyControlWithRetry {
constructor(concurrency, maxRetries = 3) {
this.concurrency = concurrency;
this.maxRetries = maxRetries;
this.queue = [];
this.running = 0;
}
async run(task) {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
let lastError;
for (let i = 0; i < this.maxRetries; i++) {
try {
const result = await task();
return result;
} catch (error) {
lastError = error;
if (i < this.maxRetries - 1) {
await new Promise(resolve =>
setTimeout(resolve, 1000 * (i + 1))
);
}
}
}
throw lastError;
}
}
// 使用示例
async function fetchWithRetry(urls, concurrency = 5) {
const control = new ConcurrencyControlWithRetry(concurrency, 3);
const promises = urls.map(url =>
control.run(() => fetch(url))
);
return Promise.all(promises);
}
```
### 2. 带超时的并发控制
```javascript
class ConcurrencyControlWithTimeout {
constructor(concurrency, timeout = 5000) {
this.concurrency = concurrency;
this.timeout = timeout;
this.queue = [];
this.running = 0;
}
async run(task) {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
const result = await Promise.race([
task(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), this.timeout)
)
]);
return result;
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// 使用示例
async function fetchWithTimeout(urls, concurrency = 5) {
const control = new ConcurrencyControlWithTimeout(concurrency, 5000);
const promises = urls.map(url =>
control.run(() => fetch(url))
);
return Promise.all(promises);
}
```
### 3. 带优先级的并发控制
```javascript
class PriorityConcurrencyControl {
constructor(concurrency) {
this.concurrency = concurrency;
this.queue = [];
this.running = 0;
}
async run(task, priority = 0) {
const taskWrapper = {
task,
priority,
resolve: null
};
this.queue.push(taskWrapper);
this.queue.sort((a, b) => b.priority - a.priority);
if (this.running >= this.concurrency) {
await new Promise(resolve => {
taskWrapper.resolve = resolve;
});
}
this.running++;
try {
return await task();
} finally {
this.running--;
const next = this.queue.find(t => t.resolve);
if (next) {
this.queue.splice(this.queue.indexOf(next), 1);
next.resolve();
}
}
}
}
// 使用示例
async function fetchWithPriority(urls, concurrency = 5) {
const control = new PriorityConcurrencyControl(concurrency);
const promises = urls.map((url, index) =>
control.run(() => fetch(url), index % 3)
);
return Promise.all(promises);
}
```
## 实际应用场景
### 1. 批量下载文件
```javascript
async function downloadFiles(urls, concurrency = 3) {
const control = new ConcurrencyControl(concurrency);
const results = await Promise.all(
urls.map(url =>
control.run(async () => {
const response = await fetch(url);
const blob = await response.blob();
return { url, blob };
})
)
);
return results;
}
```
### 2. 批量处理数据库查询
```javascript
async function processQueries(queries, concurrency = 5) {
const control = new ConcurrencyControl(concurrency);
const results = await Promise.all(
queries.map(query =>
control.run(() => database.execute(query))
)
);
return results;
}
```
### 3. 批量发送邮件
```javascript
async function sendEmails(recipients, concurrency = 10) {
const control = new ConcurrencyControl(concurrency);
const results = await Promise.allSettled(
recipients.map(recipient =>
control.run(() => emailService.send(recipient))
)
);
const successful = results.filter(r => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length;
console.log(`发送完成: 成功 ${successful}, 失败 ${failed}`);
return results;
}
```
## 性能优化技巧
### 1. 动态调整并发数
```javascript
class AdaptiveConcurrencyControl {
constructor(initialConcurrency = 5, maxConcurrency = 20) {
this.concurrency = initialConcurrency;
this.maxConcurrency = maxConcurrency;
this.minConcurrency = 1;
this.queue = [];
this.running = 0;
this.successCount = 0;
this.errorCount = 0;
}
async run(task) {
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
const result = await task();
this.successCount++;
this.adjustConcurrency();
return result;
} catch (error) {
this.errorCount++;
this.adjustConcurrency();
throw error;
} finally {
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
adjustConcurrency() {
const total = this.successCount + this.errorCount;
const errorRate = this.errorCount / total;
if (errorRate < 0.1 && this.concurrency < this.maxConcurrency) {
this.concurrency = Math.min(this.concurrency + 1, this.maxConcurrency);
} else if (errorRate > 0.3 && this.concurrency > this.minConcurrency) {
this.concurrency = Math.max(this.concurrency - 1, this.minConcurrency);
}
}
}
```
### 2. 进度监控
```javascript
class ConcurrencyControlWithProgress {
constructor(concurrency, onProgress) {
this.concurrency = concurrency;
this.onProgress = onProgress;
this.queue = [];
this.running = 0;
this.completed = 0;
this.total = 0;
}
async run(task) {
this.total++;
if (this.running >= this.concurrency) {
await new Promise(resolve => this.queue.push(resolve));
}
this.running++;
try {
const result = await task();
return result;
} finally {
this.running++;
this.completed++;
this.onProgress(this.completed, this.total);
this.running--;
const next = this.queue.shift();
if (next) next();
}
}
}
// 使用示例
async function fetchWithProgress(urls, concurrency = 5) {
const control = new ConcurrencyControlWithProgress(concurrency, (completed, total) => {
console.log(`进度: ${completed}/${total} (${(completed/total*100).toFixed(1)}%)`);
});
const promises = urls.map(url =>
control.run(() => fetch(url))
);
return Promise.all(promises);
}
```
## 常见问题
### 1. 如何选择合适的并发数?
```javascript
// 根据网络类型选择
function getOptimalConcurrency() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
return Math.min(connection.downlink || 4, 10);
}
return 4; // 默认值
}
```
### 2. 如何处理失败的任务?
```javascript
async function fetchWithPartialFailure(urls, concurrency = 5) {
const control = new ConcurrencyControl(concurrency);
const results = await Promise.allSettled(
urls.map(url =>
control.run(() => fetch(url))
)
);
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`成功: ${successful.length}, 失败: ${failed.length}`);
return { successful, failed };
}
```
## 总结
1. **避免资源耗尽**:限制同时执行的异步操作数量
2. **提高性能**:合理的并发控制可以提高整体性能
3. **灵活实现**:可以根据需求实现不同的并发控制策略
4. **错误处理**:结合重试、超时等机制提高可靠性
5. **进度监控**:实时监控任务执行进度
6. **动态调整**:根据实际情况动态调整并发数
前端 · 2月22日 14:07
如何优化 Promise 的性能?Promise 的性能优化是提升应用响应速度和用户体验的关键。通过合理使用 Promise 和相关技术,可以显著提高异步操作的效率。
## 避免不必要的 Promise 包装
### 问题示例
```javascript
// 不推荐:不必要的 Promise 包装
function fetchData() {
return new Promise((resolve) => {
resolve(fetch('/api/data'));
});
}
// 推荐:直接返回 Promise
function fetchData() {
return fetch('/api/data');
}
```
### 优化原因
不必要的 Promise 包装会增加额外的开销,包括:
- 创建新的 Promise 对象
- 额外的微任务调度
- 增加内存占用
## 并行执行独立操作
### 顺序执行(慢)
```javascript
// 不推荐:顺序执行
async function fetchAllData() {
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
return { user, posts, comments };
}
```
### 并行执行(快)
```javascript
// 推荐:并行执行
async function fetchAllData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return { user, posts, comments };
}
```
### 性能对比
假设每个请求需要 100ms:
- 顺序执行:300ms
- 并行执行:100ms(3倍提升)
## 避免过长的 Promise 链
### 问题示例
```javascript
// 不推荐:过长的 Promise 链
function processLargeData(data) {
return Promise.resolve(data)
.then(data => processData1(data))
.then(data => processData2(data))
.then(data => processData3(data))
.then(data => processData4(data))
.then(data => processData5(data))
.then(data => processData6(data))
.then(data => processData7(data))
.then(data => processData8(data));
}
```
### 优化方案
```javascript
// 推荐:使用 async/await
async function processLargeData(data) {
data = await processData1(data);
data = await processData2(data);
data = await processData3(data);
data = await processData4(data);
data = await processData5(data);
data = await processData6(data);
data = await processData7(data);
data = await processData8(data);
return data;
}
// 或者:使用函数组合
function processLargeData(data) {
return [processData1, processData2, processData3, processData4,
processData5, processData6, processData7, processData8]
.reduce((promise, processor) =>
promise.then(processor),
Promise.resolve(data)
);
}
```
## 合理使用缓存
### 基本缓存实现
```javascript
const cache = new Map();
function fetchWithCache(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url));
}
return fetch(url)
.then(response => response.json())
.then(data => {
cache.set(url, data);
return data;
});
}
```
### 带过期时间的缓存
```javascript
class PromiseCache {
constructor(ttl = 60000) {
this.cache = new Map();
this.ttl = ttl;
}
get(key) {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expiry) {
this.cache.delete(key);
return null;
}
return item.value;
}
set(key, value) {
this.cache.set(key, {
value,
expiry: Date.now() + this.ttl
});
}
async fetch(key, fetcher) {
const cached = this.get(key);
if (cached) return cached;
const value = await fetcher();
this.set(key, value);
return value;
}
}
// 使用示例
const cache = new PromiseCache(60000);
async function fetchUser(id) {
return cache.fetch(`user:${id}`, () =>
fetch(`/api/users/${id}`).then(r => r.json())
);
}
```
## 请求去重
### 基本去重实现
```javascript
const pendingRequests = new Map();
function fetchDeduplicated(url) {
if (pendingRequests.has(url)) {
return pendingRequests.get(url);
}
const promise = fetch(url)
.then(response => response.json())
.finally(() => {
pendingRequests.delete(url);
});
pendingRequests.set(url, promise);
return promise;
}
```
### 完整的去重实现
```javascript
class RequestDeduplicator {
constructor() {
this.pendingRequests = new Map();
}
async fetch(url, options = {}) {
const key = this.getRequestKey(url, options);
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
const promise = fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.finally(() => {
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
getRequestKey(url, options) {
return JSON.stringify({ url, options });
}
}
// 使用示例
const deduplicator = new RequestDeduplicator();
// 多次调用同一个 URL,只会发起一次请求
Promise.all([
deduplicator.fetch('/api/user'),
deduplicator.fetch('/api/user'),
deduplicator.fetch('/api/user')
]).then(results => {
console.log('所有请求返回相同结果:', results);
});
```
## 批量处理
### 问题示例
```javascript
// 不推荐:逐个处理
async function processItems(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
```
### 优化方案
```javascript
// 推荐:批量处理
async function processItems(items, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
```
## 错误处理优化
### 避免过度使用 try/catch
```javascript
// 不推荐:过度使用 try/catch
async function fetchData() {
try {
try {
const response = await fetch('/api/data');
try {
const data = await response.json();
try {
const processed = await processData(data);
return processed;
} catch (error) {
console.error('处理失败:', error);
}
} catch (error) {
console.error('解析失败:', error);
}
} catch (error) {
console.error('请求失败:', error);
}
} catch (error) {
console.error('未知错误:', error);
}
}
```
### 优化方案
```javascript
// 推荐:合理的错误处理
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return await processData(data);
} catch (error) {
if (error instanceof NetworkError) {
console.error('网络错误:', error.message);
} else if (error instanceof ParseError) {
console.error('解析错误:', error.message);
} else {
console.error('未知错误:', error);
}
throw error;
}
}
```
## 内存优化
### 避免内存泄漏
```javascript
// 不推荐:可能导致内存泄漏
class DataFetcher {
constructor() {
this.cache = new Map();
}
async fetch(url) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
const data = await fetch(url).then(r => r.json());
this.cache.set(url, data);
return data;
}
}
```
### 优化方案
```javascript
// 推荐:使用 WeakMap 或限制缓存大小
class DataFetcher {
constructor(maxSize = 100) {
this.cache = new Map();
this.maxSize = maxSize;
}
async fetch(url) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
const data = await fetch(url).then(r => r.json());
// 限制缓存大小
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(url, data);
return data;
}
}
```
## 性能监控
### 监控 Promise 执行时间
```javascript
function withPerformanceTracking(promise, label) {
const startTime = performance.now();
return promise
.then(result => {
const duration = performance.now() - startTime;
console.log(`${label} 完成,耗时: ${duration.toFixed(2)}ms`);
return result;
})
.catch(error => {
const duration = performance.now() - startTime;
console.error(`${label} 失败,耗时: ${duration.toFixed(2)}ms`, error);
throw error;
});
}
// 使用示例
async function fetchData() {
return withPerformanceTracking(
fetch('/api/data').then(r => r.json()),
'fetchData'
);
}
```
### 监控并发请求数
```javascript
class RequestMonitor {
constructor() {
this.activeRequests = 0;
this.maxConcurrentRequests = 0;
}
async monitor(promise) {
this.activeRequests++;
this.maxConcurrentRequests = Math.max(
this.activeRequests,
this.maxConcurrentRequests
);
try {
return await promise;
} finally {
this.activeRequests--;
}
}
getStats() {
return {
activeRequests: this.activeRequests,
maxConcurrentRequests: this.maxConcurrentRequests
};
}
}
// 使用示例
const monitor = new RequestMonitor();
async function fetchWithMonitor(url) {
return monitor.monitor(fetch(url));
}
```
## 总结
1. **避免不必要的 Promise 包装**:减少额外的开销
2. **并行执行独立操作**:利用 Promise.all 提高性能
3. **避免过长的 Promise 链**:使用 async/await 提高可读性
4. **合理使用缓存**:减少重复请求
5. **实现请求去重**:避免重复的网络请求
6. **批量处理数据**:提高处理效率
7. **优化错误处理**:避免过度嵌套的 try/catch
8. **注意内存管理**:避免内存泄漏
9. **监控性能指标**:及时发现性能问题
10. **选择合适的并发数**:根据实际情况调整并发策略
前端 · 2月22日 14:07
如何理解 Promise 的链式调用?Promise 的链式调用是 Promise 最强大的特性之一,它允许我们以优雅的方式处理多个异步操作,避免了回调地狱的问题。
## 基本概念
Promise 链式调用是指通过 `.then()` 方法返回一个新的 Promise,从而可以连续调用多个 `.then()` 方法。每个 `.then()` 方法接收前一个 Promise 的结果作为参数,并返回一个新的 Promise。
## 链式调用的工作原理
### 核心机制
1. `.then()` 方法总是返回一个新的 Promise
2. 前一个 `.then()` 的返回值会传递给下一个 `.then()`
3. 如果返回的是 Promise,会等待其完成后再传递结果
4. 错误会沿着链向下传递,直到被 `.catch()` 捕获
### 示例代码
```javascript
fetch('/api/user')
.then(response => response.json())
.then(user => {
console.log('用户信息:', user);
return fetch(`/api/posts/${user.id}`);
})
.then(response => response.json())
.then(posts => {
console.log('用户文章:', posts);
return posts;
})
.catch(error => {
console.error('发生错误:', error);
});
```
## 链式调用的返回值处理
### 1. 返回普通值
```javascript
Promise.resolve(1)
.then(value => value + 1)
.then(value => value * 2)
.then(value => console.log(value)); // 输出: 4
```
### 2. 返回 Promise
```javascript
Promise.resolve(1)
.then(value => {
return Promise.resolve(value + 1);
})
.then(value => {
return new Promise(resolve => {
setTimeout(() => resolve(value * 2), 1000);
});
})
.then(value => console.log(value)); // 输出: 4
```
### 3. 不返回值(返回 undefined)
```javascript
Promise.resolve(1)
.then(value => {
console.log(value); // 输出: 1
// 不返回任何值,相当于返回 undefined
})
.then(value => console.log(value)); // 输出: undefined
```
### 4. 抛出错误
```javascript
Promise.resolve(1)
.then(value => {
throw new Error('出错了');
})
.catch(error => {
console.error(error.message); // 输出: 出错了
return '恢复后的值';
})
.then(value => console.log(value)); // 输出: 恢复后的值
```
## 错误处理机制
### 错误传递
```javascript
Promise.resolve()
.then(() => {
throw new Error('第一个错误');
})
.then(() => {
console.log('这行不会执行');
})
.catch(error => {
console.error('捕获错误:', error.message);
return '继续执行';
})
.then(value => {
console.log(value); // 输出: 继续执行
});
```
### 多个 catch
```javascript
Promise.resolve()
.then(() => {
throw new Error('错误');
})
.catch(error => {
console.error('第一个 catch:', error.message);
throw new Error('新错误');
})
.catch(error => {
console.error('第二个 catch:', error.message);
});
```
## Promise 链式调用 vs 回调地狱
### 回调地狱(不推荐)
```javascript
fetch('/api/user', (error, response) => {
if (error) {
console.error(error);
return;
}
response.json((error, user) => {
if (error) {
console.error(error);
return;
}
fetch(`/api/posts/${user.id}`, (error, response) => {
if (error) {
console.error(error);
return;
}
response.json((error, posts) => {
if (error) {
console.error(error);
return;
}
console.log(posts);
});
});
});
});
```
### Promise 链式调用(推荐)
```javascript
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
```
## 最佳实践
### 1. 保持链的扁平
```javascript
// 不推荐:嵌套 Promise
Promise.resolve()
.then(() => {
return Promise.resolve().then(() => {
return Promise.resolve().then(() => {
console.log('嵌套太深');
});
});
});
// 推荐:扁平链式
Promise.resolve()
.then(() => {})
.then(() => {})
.then(() => console.log('扁平清晰'));
```
### 2. 合理使用 finally
```javascript
Promise.resolve()
.then(() => console.log('执行操作'))
.catch(error => console.error(error))
.finally(() => console.log('清理资源'));
```
### 3. 错误处理要全面
```javascript
Promise.resolve()
.then(data => {
// 处理数据
return processData(data);
})
.catch(error => {
// 处理错误
console.error('处理失败:', error);
// 可以返回默认值或重新抛出错误
return defaultValue;
});
```
### 4. 避免在 then 中创建不必要的 Promise
```javascript
// 不推荐
Promise.resolve(1)
.then(value => {
return new Promise(resolve => {
resolve(value + 1);
});
});
// 推荐
Promise.resolve(1)
.then(value => value + 1);
```
## 常见问题
### 1. 如何在链中传递多个值?
```javascript
Promise.all([promise1, promise2])
.then(([result1, result2]) => {
console.log(result1, result2);
});
```
### 2. 如何在链中跳过某些步骤?
```javascript
Promise.resolve()
.then(() => {
if (shouldSkip) {
return Promise.reject('skip');
}
return doSomething();
})
.catch(error => {
if (error === 'skip') {
return '跳过的结果';
}
throw error;
})
.then(result => console.log(result));
```
### 3. 如何在链中添加日志?
```javascript
Promise.resolve(1)
.tap(value => console.log('当前值:', value))
.then(value => value + 1)
.tap(value => console.log('新值:', value));
```
## 与 async/await 的对比
### Promise 链式调用
```javascript
fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
```
### async/await(更易读)
```javascript
async function fetchPosts() {
try {
const userResponse = await fetch('/api/user');
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
} catch (error) {
console.error(error);
}
}
```
async/await 本质上是 Promise 链式调用的语法糖,让异步代码看起来更像同步代码,提高了代码的可读性。
前端 · 2月22日 14:07
如何处理 Promise 的错误?Promise 的错误处理是使用 Promise 时必须掌握的重要技能。正确的错误处理可以确保程序的健壮性,避免未捕获的错误导致程序崩溃。
## 错误处理的基本方法
### 1. 使用 .catch() 方法
`.catch()` 是 Promise 错误处理的主要方法,它会捕获链中任何地方抛出的错误:
```javascript
Promise.resolve()
.then(() => {
throw new Error('出错了');
})
.catch(error => {
console.error('捕获到错误:', error.message);
});
```
### 2. 在 .then() 的第二个参数中处理
`.then()` 方法可以接收两个参数:成功回调和失败回调:
```javascript
Promise.resolve()
.then(
result => console.log('成功:', result),
error => console.error('失败:', error.message)
);
```
**注意**:这种方式的错误处理只捕获前一个 Promise 的错误,不会捕获链中后续的错误。
## 错误传播机制
### 错误会沿着 Promise 链向下传播
```javascript
Promise.resolve()
.then(() => {
throw new Error('第一个错误');
})
.then(() => {
console.log('这行不会执行');
})
.then(() => {
console.log('这行也不会执行');
})
.catch(error => {
console.error('最终捕获:', error.message); // 输出: 最终捕获: 第一个错误
});
```
### 错误可以被捕获后恢复
```javascript
Promise.resolve()
.then(() => {
throw new Error('出错了');
})
.catch(error => {
console.error('捕获错误:', error.message);
return '恢复后的值'; // 返回一个值,链可以继续
})
.then(value => {
console.log('继续执行:', value); // 输出: 继续执行: 恢复后的值
});
```
## 常见错误处理场景
### 1. 网络请求错误处理
```javascript
fetch('/api/data')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('数据:', data);
})
.catch(error => {
console.error('请求失败:', error.message);
// 可以在这里显示错误提示给用户
showErrorToUser('数据加载失败,请稍后重试');
});
```
### 2. 多个 Promise 的错误处理
```javascript
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log('全部成功:', results);
})
.catch(error => {
console.error('至少一个失败:', error.message);
});
```
### 3. 使用 Promise.allSettled 处理部分失败
```javascript
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index} 成功:`, result.value);
} else {
console.error(`Promise ${index} 失败:`, result.reason);
}
});
});
```
## 错误处理的最佳实践
### 1. 总是添加错误处理
```javascript
// 不推荐:没有错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data));
// 推荐:添加错误处理
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
```
### 2. 使用 finally 进行清理
```javascript
let isLoading = true;
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
isLoading = false;
console.log('请求完成,无论成功或失败');
});
```
### 3. 错误处理要具体
```javascript
// 不推荐:笼统的错误处理
Promise.resolve()
.catch(error => {
console.error('出错了');
});
// 推荐:具体的错误处理
Promise.resolve()
.catch(error => {
if (error instanceof NetworkError) {
console.error('网络错误:', error.message);
} else if (error instanceof ValidationError) {
console.error('验证错误:', error.message);
} else {
console.error('未知错误:', error.message);
}
});
```
### 4. 考虑错误恢复策略
```javascript
async function fetchDataWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`尝试 ${i + 1} 失败:`, error.message);
if (i === maxRetries - 1) {
throw error; // 最后一次尝试失败,抛出错误
}
// 等待一段时间后重试
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
```
## async/await 中的错误处理
### 使用 try/catch
```javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log('数据:', data);
} catch (error) {
console.error('错误:', error.message);
// 错误处理逻辑
}
}
```
### 捕获特定错误
```javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError') {
console.error('网络连接问题');
} else if (error.message.includes('HTTP error')) {
console.error('服务器错误');
} else {
console.error('未知错误:', error);
}
throw error; // 可以选择重新抛出错误
}
}
```
## 未捕获的 Promise 错误
### 全局错误处理
```javascript
// 处理未捕获的 Promise 错误
window.addEventListener('unhandledrejection', event => {
console.error('未捕获的 Promise 错误:', event.reason);
// 可以在这里记录错误或显示错误提示
event.preventDefault(); // 阻止默认的错误输出
});
// Node.js 环境中
process.on('unhandledRejection', (reason, promise) => {
console.error('未捕获的 Promise 错误:', reason);
});
```
## 常见错误处理陷阱
### 1. 忘记在链中添加 catch
```javascript
// 危险:没有错误处理
Promise.reject('出错了')
.then(result => console.log(result));
// 错误会冒泡到全局,可能导致程序崩溃
// 安全:添加错误处理
Promise.reject('出错了')
.then(result => console.log(result))
.catch(error => console.error(error));
```
### 2. 在 catch 中忘记重新抛出错误
```javascript
// 可能导致问题:错误被吞掉
Promise.reject('出错了')
.catch(error => {
console.error('捕获错误:', error);
// 忘记重新抛出,后续代码会继续执行
})
.then(() => {
console.log('这行会执行,即使前面有错误');
});
// 推荐:根据情况决定是否重新抛出
Promise.reject('出错了')
.catch(error => {
console.error('捕获错误:', error);
// 如果错误无法恢复,重新抛出
throw error;
});
```
### 3. 混用 then 的第二个参数和 catch
```javascript
// 不推荐:容易混淆
Promise.resolve()
.then(
result => console.log('成功'),
error => console.error('失败1')
)
.catch(error => console.error('失败2'));
// 推荐:统一使用 catch
Promise.resolve()
.then(result => console.log('成功'))
.catch(error => console.error('失败'));
```
## 总结
1. **总是添加错误处理**:使用 `.catch()` 或 `try/catch`
2. **理解错误传播**:错误会沿着 Promise 链向下传播
3. **使用 finally 清理**:无论成功或失败都要执行的代码放在 `finally` 中
4. **具体化错误处理**:根据错误类型进行不同的处理
5. **考虑错误恢复**:实现重试机制或降级策略
6. **避免错误吞掉**:确保错误被正确处理或重新抛出
7. **全局错误监控**:使用全局错误处理器捕获未处理的错误
前端 · 2月22日 14:07
Promise.all() 和 Promise.race() 的区别是什么?Promise.all() 和 Promise.race() 是 Promise 提供的两个重要的静态方法,它们用于处理多个 Promise 的并行执行,但行为和用途完全不同。
## Promise.all()
### 基本概念
Promise.all() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 会在所有输入的 Promise 都成功完成时才成功,返回的结果是所有 Promise 结果组成的数组(顺序与输入顺序一致)。如果任何一个 Promise 失败,Promise.all() 会立即失败,返回第一个失败的 Promise 的错误。
### 使用场景
- 需要同时发起多个独立的请求,并等待所有请求都完成
- 多个异步操作之间存在依赖关系,需要所有结果都准备好后才能继续
### 示例代码
```javascript
const promise1 = fetch('/api/user');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');
Promise.all([promise1, promise2, promise3])
.then(responses => {
// 所有请求都成功
return Promise.all(responses.map(r => r.json()));
})
.then(data => {
console.log('所有数据:', data);
})
.catch(error => {
console.error('某个请求失败:', error);
});
```
### 特点
1. **并行执行**: 所有 Promise 同时开始执行
2. **顺序保证**: 结果数组顺序与输入顺序一致
3. **快速失败**: 任何一个失败,整体立即失败
4. **空数组**: 如果传入空数组,立即返回成功
## Promise.race()
### 基本概念
Promise.race() 同样接收一个 Promise 数组,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 完成(无论成功或失败)时立即完成,并返回第一个完成的 Promise 的结果或错误。
### 使用场景
- 设置超时机制
- 从多个数据源获取数据,使用最快返回的结果
- 竞争条件处理
### 示例代码
```javascript
// 超时示例
const fetchData = fetch('/api/data');
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 5000);
});
Promise.race([fetchData, timeout])
.then(response => console.log('数据获取成功'))
.catch(error => console.error(error));
// 多数据源竞争示例
const source1 = fetch('https://api1.example.com/data');
const source2 = fetch('https://api2.example.com/data');
const source3 = fetch('https://api3.example.com/data');
Promise.race([source1, source2, source3])
.then(response => response.json())
.then(data => console.log('最快的数据源:', data));
```
### 特点
1. **快速返回**: 返回第一个完成的结果
2. **状态继承**: 成功或失败取决于第一个完成的 Promise
3. **不确定性**: 哪个 Promise 先完成是不确定的
4. **空数组**: 如果传入空数组,Promise 永远处于 pending 状态
## 对比总结
| 特性 | Promise.all() | Promise.race() |
|------|---------------|----------------|
| 完成条件 | 所有 Promise 都成功 | 第一个 Promise 完成 |
| 失败条件 | 任何一个 Promise 失败 | 第一个 Promise 失败 |
| 返回结果 | 所有结果的数组 | 第一个完成的结果 |
| 使用场景 | 需要所有结果 | 需要最快结果 |
| 执行方式 | 并行执行 | 并行执行 |
## 其他相关方法
### Promise.allSettled()
ES2020 新增,等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果:
```javascript
Promise.allSettled([promise1, promise2, promise3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
```
### Promise.any()
ES2021 新增,返回第一个成功的 Promise,如果所有 Promise 都失败,返回 AggregateError:
```javascript
Promise.any([promise1, promise2, promise3])
.then(result => console.log('第一个成功:', result))
.catch(error => console.log('全部失败:', error));
```
## 实际应用建议
1. **使用 Promise.all()**: 当需要所有数据都准备好时,如加载多个必需的资源
2. **使用 Promise.race()**: 当只需要最快的结果时,如设置超时或从多个镜像源获取数据
3. **使用 Promise.allSettled()**: 当需要知道每个操作的结果,即使某些失败
4. **使用 Promise.any()**: 当只需要一个成功的结果,如从多个备份服务器获取数据
前端 · 2月22日 14:07
Promise.any() 的作用是什么?Promise.any() 是 ES2021 引入的 Promise 静态方法,它接收一个 Promise 数组,返回第一个成功完成的 Promise 的结果。如果所有 Promise 都失败,则返回 AggregateError。
## 基本概念
Promise.any() 接收一个可迭代的 Promise 对象作为参数,返回一个新的 Promise。这个新的 Promise 会在第一个 Promise 成功完成时立即完成,返回该 Promise 的结果。如果所有 Promise 都失败,则返回一个 AggregateError,包含所有失败的原因。
## 基本用法
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.reject('错误2');
const promise3 = Promise.resolve('成功');
Promise.any([promise1, promise2, promise3])
.then(result => {
console.log(result); // 输出: 成功
})
.catch(error => {
console.error(error);
});
```
## 与其他方法的对比
### Promise.any() vs Promise.race()
**Promise.any()**: 返回第一个成功的 Promise
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.resolve('另一个成功');
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)); // 输出: 成功
```
**Promise.race()**: 返回第一个完成的 Promise(无论成功或失败)
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.resolve('另一个成功');
Promise.race([promise1, promise2, promise3])
.then(result => console.log(result))
.catch(error => console.error(error)); // 输出: 错误1
```
### Promise.any() vs Promise.all()
**Promise.any()**: 只要有一个成功就返回
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.reject('错误3');
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)); // 输出: 成功
```
**Promise.all()**: 必须全部成功才返回
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.reject('错误3');
Promise.all([promise1, promise2, promise3])
.then(result => console.log(result))
.catch(error => console.error(error)); // 输出: 错误1
```
### Promise.any() vs Promise.allSettled()
**Promise.any()**: 返回第一个成功的结果
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.reject('错误3');
Promise.any([promise1, promise2, promise3])
.then(result => console.log(result)); // 输出: 成功
```
**Promise.allSettled()**: 返回所有 Promise 的状态
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.reject('错误3');
Promise.allSettled([promise1, promise2, promise3])
.then(results => console.log(results));
// 输出:
// [
// { status: 'rejected', reason: '错误1' },
// { status: 'fulfilled', value: '成功' },
// { status: 'rejected', reason: '错误3' }
// ]
```
## AggregateError
当所有 Promise 都失败时,Promise.any() 会返回一个 AggregateError,包含所有失败的原因。
### 基本用法
```javascript
const promise1 = Promise.reject('错误1');
const promise2 = Promise.reject('错误2');
const promise3 = Promise.reject('错误3');
Promise.any([promise1, promise2, promise3])
.catch(error => {
console.error(error instanceof AggregateError); // true
console.error(error.message); // All promises were rejected
console.error(error.errors); // ['错误1', '错误2', '错误3']
});
```
### 处理 AggregateError
```javascript
async function fetchFromMultipleSources(urls) {
try {
const response = await Promise.any(
urls.map(url => fetch(url))
);
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有数据源都失败了:');
error.errors.forEach((err, index) => {
console.error(` ${urls[index]}: ${err.message}`);
});
throw new Error('无法从任何数据源获取数据');
}
throw error;
}
}
```
## 实际应用场景
### 1. 从多个数据源获取数据,使用最快成功的
```javascript
async function fetchFromFastestSource(sources) {
try {
const response = await Promise.any(
sources.map(source => fetch(source.url))
);
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有数据源都失败了');
throw new Error('无法获取数据');
}
throw error;
}
}
// 使用示例
const sources = [
{ name: '主服务器', url: 'https://api1.example.com/data' },
{ name: '备用服务器1', url: 'https://api2.example.com/data' },
{ name: '备用服务器2', url: 'https://api3.example.com/data' }
];
fetchFromFastestSource(sources)
.then(data => console.log('数据:', data))
.catch(error => console.error(error));
```
### 2. 实现超时机制
```javascript
function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), timeout);
});
return Promise.any([
fetch(url).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}),
timeoutPromise
]);
}
// 使用示例
fetchWithTimeout('/api/data', 3000)
.then(data => console.log('数据:', data))
.catch(error => {
if (error.message === 'Timeout') {
console.error('请求超时');
} else {
console.error('请求失败:', error);
}
});
```
### 3. 尝试多个备份方案
```javascript
async function tryMultipleStrategies(strategies) {
try {
return await Promise.any(
strategies.map(strategy => strategy())
);
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有策略都失败了');
throw new Error('无法完成任务');
}
throw error;
}
}
// 使用示例
const strategies = [
() => fetch('/api/v1/data').then(r => r.json()),
() => fetch('/api/v2/data').then(r => r.json()),
() => fetch('/api/v3/data').then(r => r.json())
];
tryMultipleStrategies(strategies)
.then(data => console.log('数据:', data))
.catch(error => console.error(error));
```
### 4. 图片加载,使用第一个成功加载的
```javascript
async function loadFirstSuccessfulImage(imageUrls) {
try {
const imagePromises = imageUrls.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load: ${url}`));
img.src = url;
});
});
return await Promise.any(imagePromises);
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有图片都加载失败');
throw new Error('无法加载图片');
}
throw error;
}
}
// 使用示例
const imageUrls = [
'https://cdn1.example.com/image.jpg',
'https://cdn2.example.com/image.jpg',
'https://cdn3.example.com/image.jpg'
];
loadFirstSuccessfulImage(imageUrls)
.then(img => document.body.appendChild(img))
.catch(error => console.error(error));
```
## 错误处理
### 处理所有失败的情况
```javascript
async function fetchWithFallback(urls) {
try {
const response = await Promise.any(
urls.map(url => fetch(url))
);
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有 URL 都失败了');
// 可以返回默认值或重新抛出错误
return { error: 'All sources failed' };
}
throw error;
}
}
```
### 记录所有失败的原因
```javascript
async function fetchWithLogging(urls) {
try {
const response = await Promise.any(
urls.map(url => fetch(url))
);
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有 URL 都失败了:');
error.errors.forEach((err, index) => {
console.error(` ${urls[index]}: ${err.message}`);
});
throw new Error('无法获取数据');
}
throw error;
}
}
```
## 性能考虑
### 空数组处理
```javascript
Promise.any([])
.then(result => console.log(result))
.catch(error => {
console.error(error instanceof AggregateError); // true
console.error(error.errors); // []
});
```
### 非 Promise 值处理
```javascript
Promise.any([1, 2, Promise.resolve(3)])
.then(result => console.log(result)); // 输出: 1
```
## 浏览器兼容性
Promise.any() 是 ES2021 引入的,现代浏览器都支持:
- Chrome: 85+
- Firefox: 79+
- Safari: 14+
- Edge: 85+
对于旧浏览器,可以使用 polyfill:
```javascript
if (!Promise.any) {
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => resolve(value),
error => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
if (promises.length === 0) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
};
}
```
## 最佳实践
### 1. 总是处理 AggregateError
```javascript
async function fetchData(sources) {
try {
return await Promise.any(sources.map(s => fetch(s)));
} catch (error) {
if (error instanceof AggregateError) {
console.error('所有数据源都失败了');
throw new Error('无法获取数据');
}
throw error;
}
}
```
### 2. 提供降级方案
```javascript
async function fetchWithFallback(sources, fallbackData) {
try {
const response = await Promise.any(sources.map(s => fetch(s)));
return await response.json();
} catch (error) {
if (error instanceof AggregateError) {
console.warn('所有数据源都失败,使用降级数据');
return fallbackData;
}
throw error;
}
}
```
### 3. 记录失败原因
```javascript
async function fetchWithLogging(sources) {
try {
return await Promise.any(sources.map(s => fetch(s)));
} catch (error) {
if (error instanceof AggregateError) {
error.errors.forEach((err, index) => {
console.error(`${sources[index]} 失败:`, err.message);
});
throw new Error('无法获取数据');
}
throw error;
}
}
```
## 总结
1. **返回第一个成功的 Promise**:只要有一个成功就立即返回
2. **AggregateError 处理所有失败**:所有 Promise 都失败时返回 AggregateError
3. **适合多数据源场景**:从多个数据源获取数据,使用最快成功的
4. **与 Promise.race() 不同**:只返回成功的结果,不返回失败的结果
5. **与 Promise.all() 不同**:不需要全部成功,只要一个成功即可
6. **提供降级方案**:所有失败时可以提供降级数据
7. **记录失败原因**:AggregateError 包含所有失败的原因
服务端 · 2月22日 14:07