Promises handle asynchronous operations elegantly. Here's everything you need to know.
Promise Basics#
1// Creating a promise
2const promise = new Promise((resolve, reject) => {
3 // Async operation
4 setTimeout(() => {
5 const success = true;
6
7 if (success) {
8 resolve('Operation completed');
9 } else {
10 reject(new Error('Operation failed'));
11 }
12 }, 1000);
13});
14
15// Consuming a promise
16promise
17 .then(result => {
18 console.log(result);
19 })
20 .catch(error => {
21 console.error(error);
22 })
23 .finally(() => {
24 console.log('Cleanup');
25 });
26
27// Promise states
28// Pending: initial state
29// Fulfilled: resolved successfully
30// Rejected: operation failedPromise Static Methods#
1// Promise.resolve - create resolved promise
2const resolved = Promise.resolve('value');
3resolved.then(v => console.log(v)); // 'value'
4
5// Promise.reject - create rejected promise
6const rejected = Promise.reject(new Error('error'));
7rejected.catch(e => console.error(e));
8
9// Promise.all - wait for all, fail if any fails
10const results = await Promise.all([
11 fetch('/api/users'),
12 fetch('/api/posts'),
13 fetch('/api/comments'),
14]);
15// All responses or first rejection
16
17// Promise.allSettled - wait for all, never rejects
18const results = await Promise.allSettled([
19 fetch('/api/users'),
20 fetch('/api/posts'),
21 Promise.reject('error'),
22]);
23// [
24// { status: 'fulfilled', value: Response },
25// { status: 'fulfilled', value: Response },
26// { status: 'rejected', reason: 'error' }
27// ]
28
29// Promise.race - first to settle (resolve or reject)
30const first = await Promise.race([
31 fetch('/api/slow'),
32 timeout(5000),
33]);
34
35// Promise.any - first to resolve (ignores rejections)
36const fastest = await Promise.any([
37 fetch('https://cdn1.example.com/resource'),
38 fetch('https://cdn2.example.com/resource'),
39 fetch('https://cdn3.example.com/resource'),
40]);
41// First successful responseChaining#
1// Each then returns a new promise
2fetchUser(userId)
3 .then(user => fetchUserPosts(user.id))
4 .then(posts => fetchPostComments(posts[0].id))
5 .then(comments => {
6 console.log(comments);
7 })
8 .catch(error => {
9 console.error('Error in chain:', error);
10 });
11
12// Return values are wrapped in Promise.resolve
13Promise.resolve(1)
14 .then(x => x + 1) // Returns 2
15 .then(x => x * 2) // Returns 4
16 .then(x => {
17 throw new Error('Oops');
18 })
19 .then(x => x + 1) // Skipped
20 .catch(e => 0) // Returns 0
21 .then(x => x + 10) // Returns 10
22 .then(console.log); // Logs 10
23
24// Returning promises in chain
25fetchUser(1)
26 .then(user => {
27 // Returning a promise
28 return fetchProfile(user.id);
29 })
30 .then(profile => {
31 // profile is the resolved value
32 console.log(profile);
33 });Error Handling#
1// Catch handles rejections
2fetchData()
3 .then(data => processData(data))
4 .catch(error => {
5 console.error('Error:', error);
6 return defaultValue; // Recovery
7 })
8 .then(result => {
9 // Continues with recovered value or processed data
10 });
11
12// Multiple catch blocks
13fetchData()
14 .then(data => {
15 if (!data) throw new ValidationError('No data');
16 return processData(data);
17 })
18 .catch(error => {
19 if (error instanceof ValidationError) {
20 return handleValidationError(error);
21 }
22 throw error; // Re-throw other errors
23 })
24 .catch(error => {
25 // Handles re-thrown and other errors
26 console.error('Unhandled:', error);
27 });
28
29// Error boundaries with Promise.all
30async function fetchAllSafe(urls) {
31 const results = await Promise.allSettled(
32 urls.map(url => fetch(url).then(r => r.json()))
33 );
34
35 return results.map(result => {
36 if (result.status === 'fulfilled') {
37 return { success: true, data: result.value };
38 }
39 return { success: false, error: result.reason };
40 });
41}Async/Await#
1// Async functions return promises
2async function fetchUserData(userId) {
3 const response = await fetch(`/api/users/${userId}`);
4 const user = await response.json();
5 return user;
6}
7
8// Error handling with try/catch
9async function safeOperation() {
10 try {
11 const result = await riskyOperation();
12 return result;
13 } catch (error) {
14 console.error('Operation failed:', error);
15 return defaultValue;
16 } finally {
17 cleanup();
18 }
19}
20
21// Parallel execution
22async function fetchAllParallel() {
23 // Sequential (slow)
24 const users = await fetchUsers();
25 const posts = await fetchPosts();
26
27 // Parallel (fast)
28 const [users, posts] = await Promise.all([
29 fetchUsers(),
30 fetchPosts(),
31 ]);
32
33 return { users, posts };
34}
35
36// Awaiting multiple with error handling
37async function fetchWithFallbacks() {
38 const results = await Promise.allSettled([
39 fetchPrimary(),
40 fetchFallback(),
41 ]);
42
43 const successful = results
44 .filter(r => r.status === 'fulfilled')
45 .map(r => r.value);
46
47 return successful[0] || null;
48}Advanced Patterns#
1// Timeout wrapper
2function withTimeout(promise, ms) {
3 const timeout = new Promise((_, reject) => {
4 setTimeout(() => reject(new Error('Timeout')), ms);
5 });
6
7 return Promise.race([promise, timeout]);
8}
9
10await withTimeout(fetch('/api/slow'), 5000);
11
12// Retry with exponential backoff
13async function retry(fn, retries = 3, delay = 1000) {
14 for (let i = 0; i < retries; i++) {
15 try {
16 return await fn();
17 } catch (error) {
18 if (i === retries - 1) throw error;
19 await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
20 }
21 }
22}
23
24await retry(() => fetch('/api/flaky'), 3, 1000);
25
26// Deferred promise
27function createDeferred() {
28 let resolve, reject;
29 const promise = new Promise((res, rej) => {
30 resolve = res;
31 reject = rej;
32 });
33 return { promise, resolve, reject };
34}
35
36const deferred = createDeferred();
37// Later...
38deferred.resolve('value');
39await deferred.promise;
40
41// Serial execution
42async function serial(tasks) {
43 const results = [];
44 for (const task of tasks) {
45 results.push(await task());
46 }
47 return results;
48}
49
50// Concurrent with limit
51async function concurrent(tasks, limit) {
52 const results = [];
53 const executing = new Set();
54
55 for (const task of tasks) {
56 const promise = task().then(result => {
57 executing.delete(promise);
58 return result;
59 });
60
61 executing.add(promise);
62 results.push(promise);
63
64 if (executing.size >= limit) {
65 await Promise.race(executing);
66 }
67 }
68
69 return Promise.all(results);
70}Promise Queue#
1class PromiseQueue {
2 constructor(concurrency = 1) {
3 this.concurrency = concurrency;
4 this.pending = [];
5 this.running = 0;
6 }
7
8 add(fn) {
9 return new Promise((resolve, reject) => {
10 this.pending.push({ fn, resolve, reject });
11 this.process();
12 });
13 }
14
15 process() {
16 while (this.running < this.concurrency && this.pending.length > 0) {
17 const { fn, resolve, reject } = this.pending.shift();
18 this.running++;
19
20 fn()
21 .then(resolve)
22 .catch(reject)
23 .finally(() => {
24 this.running--;
25 this.process();
26 });
27 }
28 }
29}
30
31const queue = new PromiseQueue(3);
32
33const results = await Promise.all([
34 queue.add(() => fetch('/api/1')),
35 queue.add(() => fetch('/api/2')),
36 queue.add(() => fetch('/api/3')),
37 queue.add(() => fetch('/api/4')),
38 queue.add(() => fetch('/api/5')),
39]);
40// Max 3 concurrent requestsCommon Patterns#
1// Caching promise results
2const cache = new Map();
3
4async function fetchCached(url) {
5 if (cache.has(url)) {
6 return cache.get(url);
7 }
8
9 const promise = fetch(url).then(r => r.json());
10 cache.set(url, promise);
11 return promise;
12}
13
14// Debounced promise
15function debouncePromise(fn, delay) {
16 let timeout;
17 let pendingPromise;
18
19 return function(...args) {
20 clearTimeout(timeout);
21
22 return new Promise((resolve, reject) => {
23 timeout = setTimeout(async () => {
24 try {
25 const result = await fn.apply(this, args);
26 resolve(result);
27 } catch (error) {
28 reject(error);
29 }
30 }, delay);
31 });
32 };
33}
34
35const debouncedSearch = debouncePromise(searchAPI, 300);
36
37// Promise memoization
38function memoizeAsync(fn) {
39 const cache = new Map();
40
41 return async function(...args) {
42 const key = JSON.stringify(args);
43
44 if (cache.has(key)) {
45 return cache.get(key);
46 }
47
48 const result = await fn.apply(this, args);
49 cache.set(key, result);
50 return result;
51 };
52}Best Practices#
Error Handling:
✓ Always handle rejections
✓ Use try/catch with async/await
✓ Provide meaningful error messages
✓ Consider error recovery strategies
Performance:
✓ Use Promise.all for parallel execution
✓ Avoid unnecessary sequential awaits
✓ Implement timeouts for network calls
✓ Consider request caching
Patterns:
✓ Use Promise.allSettled for mixed results
✓ Implement retry logic for flaky operations
✓ Use queues to limit concurrency
✓ Cancel unnecessary promises when possible
Conclusion#
Promises provide powerful abstractions for async code. Use Promise.all for parallel operations, implement proper error handling, and leverage async/await for cleaner syntax. Master patterns like retry, timeout, and queue for robust applications.