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
- Always return Promise: Let callers chain calls
- Use async/await: Improve code readability
- Avoid nested Promises: Keep code flat
- Handle errors: Always add error handling
- Use Promise.all appropriately: For independent async operations
- Use Promise.allSettled: For partial failure scenarios
- Use Promise.race: Implement timeout and race conditions
- Use Promise.any: Get first successful result
- Use finally: Perform cleanup work
- Implement retry mechanism: Improve reliability
- Use AbortController: Cancel requests
- Avoid unnecessary await: Execute independent async operations in parallel
- Use caching: Reduce duplicate requests
- Implement request deduplication: Avoid making same requests simultaneously