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

What are common pitfalls and best practices of Promise?

2月22日 14:07

Common pitfalls and best practices of Promise are knowledge that every JavaScript developer should master. Understanding these pitfalls helps us write more robust and efficient asynchronous code.

Common Pitfalls

1. Forgetting to Return Promise

Problem Example:

javascript
// Not recommended: forgetting to return Promise function fetchData() { fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); // Forgot to return data }); } // Caller cannot get data fetchData().then(data => { console.log(data); // undefined });

Correct Approach:

javascript
// Recommended: return Promise function fetchData() { return fetch('/api/data') .then(response => response.json()) .then(data => { console.log(data); return data; // Return data }); } // Caller can get data fetchData().then(data => { console.log(data); // Actual data });

2. Nesting Promises in then

Problem Example:

javascript
// Not recommended: nested Promises fetch('/api/user') .then(response => response.json()) .then(user => { fetch(`/api/posts/${user.id}`) .then(response => response.json()) .then(posts => { console.log(posts); }); });

Correct Approach:

javascript
// Recommended: flat chaining fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log(posts));

3. Forgetting to Handle Errors

Problem Example:

javascript
// Not recommended: no error handling fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)); // If request fails, error is ignored

Correct Approach:

javascript
// Recommended: add error handling fetch('/api/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Request failed:', error));

4. Sequential await in Loop

Problem Example:

javascript
// Not recommended: sequential execution, slow async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; }

Correct Approach:

javascript
// Recommended: parallel execution, fast async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises); }

5. Mixing async/await and Promise.then()

Problem Example:

javascript
// Not recommended: mixing causes confusion async function fetchData() { const response = await fetch('/api/data'); return response.json().then(data => { console.log(data); return data; }); }

Correct Approach:

javascript
// Recommended: use async/await consistently async function fetchData() { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); return data; }

6. Unnecessary Promise Wrapping

Problem Example:

javascript
// Not recommended: unnecessary Promise wrapping function fetchData() { return new Promise((resolve) => { fetch('/api/data') .then(response => response.json()) .then(data => resolve(data)); }); }

Correct Approach:

javascript
// Recommended: return Promise directly function fetchData() { return fetch('/api/data') .then(response => response.json()); }

7. Executing Async Operations in Constructor

Problem Example:

javascript
// Not recommended: async operations in constructor 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; // May return null } }

Correct Approach:

javascript
// Recommended: use static factory method or init method 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; // Guaranteed to have data } } // Usage const user = await User.create(1); console.log(user.getData());

8. Overusing Promise.all

Problem Example:

javascript
// Not recommended: using Promise.all for unrelated operations async function fetchData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); // If only user data is needed, other requests are wasteful return user; }

Correct Approach:

javascript
// Recommended: only request needed data async function fetchData() { const user = await fetchUser(); return user; } // Or: load on demand async function fetchAllData() { const user = await fetchUser(); // Only load other data when needed if (user.hasPosts) { const posts = await fetchPosts(); return { user, posts }; } return { user }; }

Best Practices

1. Always Return Promise

javascript
// Recommended: function always returns Promise function fetchData() { return fetch('/api/data') .then(response => response.json()); } // Caller can chain calls fetchData() .then(data => processData(data)) .then(result => console.log(result));

2. Use async/await for Readability

javascript
// Recommended: use 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('Failed to fetch data:', error); throw error; } }

3. Use Promise.all Appropriately

javascript
// Recommended: use Promise.all for independent async operations async function fetchAllData() { const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]); return { user, posts, comments }; }

4. Use Promise.allSettled for Partial Failures

javascript
// Recommended: use 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. Use Promise.race for Timeout

javascript
// Recommended: use Promise.race for timeout function fetchWithTimeout(url, timeout = 5000) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Timeout')), timeout); }); return Promise.race([ fetch(url), timeoutPromise ]); }

6. Use Promise.any for First Successful Result

javascript
// Recommended: use 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('All data sources failed'); throw new Error('Cannot fetch data'); } throw error; } }

7. Use finally for Cleanup

javascript
// Recommended: use finally async function fetchDataWithCleanup() { let connection; try { connection = await createConnection(); const data = await connection.query('SELECT * FROM users'); return data; } catch (error) { console.error('Query failed:', error); throw error; } finally { if (connection) { await connection.close(); } } }

8. Avoid Creating Promises in Loop

Problem Example:

javascript
// Not recommended: creating Promises in loop 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); }

Correct Approach:

javascript
// Recommended: use map to create Promises function processItems(items) { const promises = items.map(item => new Promise((resolve) => { setTimeout(() => { resolve(processItem(item)); }, 1000); }) ); return Promise.all(promises); }

9. Use AbortController to Cancel Requests

javascript
// Recommended: use 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('Request cancelled'); return null; } throw error; } } cancel() { this.controller.abort(); } }

10. Implement Retry Mechanism

javascript
// Recommended: implement retry mechanism 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(`Attempt ${i + 1} failed:`, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)) ); } } }

Performance Optimization

1. Avoid Unnecessary await

javascript
// Not recommended: unnecessary 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 }; } // Recommended: parallel execution async function fetchData() { const [data1, data2, data3] = await Promise.all([ fetch('/api/data1'), fetch('/api/data2'), fetch('/api/data3') ]); return { data1, data2, data3 }; }

2. Use Caching

javascript
// Recommended: use caching 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. Implement Request Deduplication

javascript
// Recommended: implement request deduplication 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; }

Summary

  1. Always return Promise: Let callers chain calls
  2. Use async/await: Improve code readability
  3. Avoid nested Promises: Keep code flat
  4. Handle errors: Always add error handling
  5. Use Promise.all appropriately: For independent async operations
  6. Use Promise.allSettled: For partial failure scenarios
  7. Use Promise.race: Implement timeout and race conditions
  8. Use Promise.any: Get first successful result
  9. Use finally: Perform cleanup work
  10. Implement retry mechanism: Improve reliability
  11. Use AbortController: Cancel requests
  12. Avoid unnecessary await: Execute independent async operations in parallel
  13. Use caching: Reduce duplicate requests
  14. Implement request deduplication: Avoid making same requests simultaneously
标签:Promise