乐闻世界logo
搜索文章和话题

Promise 的常见陷阱和最佳实践有哪些?

2月22日 14:07

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

shell
```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. 实现请求去重:避免同时发起相同的请求
标签:Promise