Promises in JavaScript ⏳
Most people jump straight into async/await without understanding what a Promise really is. A Promise is just an object that represents a value you'll get later. It has three states: pending, fulfilled, and rejected.
This guide will take you from the basics to advanced Promise patterns, helping you understand the foundation of asynchronous JavaScript before diving into async/await.
Table of content
- What is a Promise?
- The Three States of a Promise
- Creating Promises
- Using .then(), .catch(), and .finally()
- Promise Chaining
- Promise.all(), Promise.race(), and Promise.allSettled()
- Error Handling in Promises
- Advanced Promise Patterns
1. What is a Promise?
A Promise is a JavaScript object that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Think of it as a placeholder for a value that will be available in the future.
Key Points:
- A Promise is an object, not a function or a value
- It represents a value that will be available later
- It helps you handle asynchronous operations more elegantly than callbacks
- Once a Promise is settled (fulfilled or rejected), its state cannot change
1// A Promise is an object representing a value you'll get later
2// It has three states: pending, fulfilled, and rejected
3
4// Creating a Promise
5const myPromise = new Promise((resolve, reject) => {
6 // Simulate async operation
7 setTimeout(() => {
8 const success = true;
9 if (success) {
10 resolve('Operation completed!'); // Fulfilled state
11 } else {
12 reject('Operation failed!'); // Rejected state
13 }
14 }, 1000);
15});
16
17// Promise states:
18// 1. Pending: Initial state, neither fulfilled nor rejected
19// 2. Fulfilled: Operation completed successfully
20// 3. Rejected: Operation failed
21
22// Using the Promise
23myPromise
24 .then((result) => {
25 console.log(result); // "Operation completed!"
26 })
27 .catch((error) => {
28 console.error(error); // "Operation failed!"
29 });
30
31// Simple example: Fetching data
32const fetchUser = new Promise((resolve, reject) => {
33 setTimeout(() => {
34 resolve({ name: 'John', age: 30 });
35 }, 2000);
36});
37
38fetchUser.then(user => {
39 console.log(user); // { name: 'John', age: 30 }
40});2. The Three States of a Promise
Every Promise has one of three states:
1. Pending
The initial state. The Promise is neither fulfilled nor rejected. It's waiting for the asynchronous operation to complete.
2. Fulfilled (Resolved)
The operation completed successfully. The Promise has a resulting value.
3. Rejected
The operation failed. The Promise has a reason for the failure.
Important: Once a Promise transitions from pending to either fulfilled or rejected, it cannot change states again. It's immutable.
1// Understanding Promise States
2
3// 1. PENDING - Initial state
4const pendingPromise = new Promise((resolve, reject) => {
5 // Promise is pending until resolve or reject is called
6 console.log('Promise is pending...');
7 // Not calling resolve/reject yet - stays pending
8});
9
10// 2. FULFILLED - Successfully completed
11const fulfilledPromise = Promise.resolve('Success!');
12// Immediately fulfilled with value 'Success!'
13
14fulfilledPromise.then(value => {
15 console.log(value); // "Success!"
16});
17
18// 3. REJECTED - Failed
19const rejectedPromise = Promise.reject('Error occurred!');
20// Immediately rejected with error 'Error occurred!'
21
22rejectedPromise.catch(error => {
23 console.error(error); // "Error occurred!"
24});
25
26// Checking Promise state (using a helper)
27function checkPromiseState(promise) {
28 // Note: There's no direct way to check state in JavaScript
29 // But we can infer it from behavior
30
31 let state = 'pending';
32
33 promise
34 .then(() => {
35 state = 'fulfilled';
36 console.log('Promise is fulfilled');
37 })
38 .catch(() => {
39 state = 'rejected';
40 console.log('Promise is rejected');
41 });
42
43 return state;
44}
45
46// Example: Promise that transitions from pending to fulfilled
47const asyncOperation = new Promise((resolve) => {
48 setTimeout(() => {
49 resolve('Data loaded');
50 }, 1000);
51});
52
53console.log('Promise created - state: pending');
54asyncOperation.then(result => {
55 console.log('Promise resolved - state: fulfilled');
56 console.log('Result:', result);
57});3. Creating Promises
You create a Promise using the new Promise() constructor, which takes an executor function with two parameters: resolve and reject.
Executor Function:
resolve(value)- Call this when the operation succeedsreject(reason)- Call this when the operation fails- The executor runs immediately when the Promise is created
Quick Promise Creation:
Promise.resolve(value)- Creates an immediately fulfilled PromisePromise.reject(reason)- Creates an immediately rejected Promise
4. Using .then(), .catch(), and .finally()
These are the three main methods for handling Promises:
- .then(onFulfilled, onRejected) - Handles fulfilled promises. Returns a new Promise, allowing chaining.
- .catch(onRejected) - Handles rejected promises. It's essentially
.then(null, onRejected). - .finally(onFinally) - Executes regardless of the outcome (fulfilled or rejected). Useful for cleanup.
1// Using .then() and .catch()
2
3// .then() - handles fulfilled promises
4const promise = new Promise((resolve, reject) => {
5 setTimeout(() => resolve('Success!'), 1000);
6});
7
8promise.then((value) => {
9 console.log(value); // "Success!"
10});
11
12// .catch() - handles rejected promises
13const failingPromise = new Promise((resolve, reject) => {
14 setTimeout(() => reject('Error!'), 1000);
15});
16
17failingPromise
18 .then((value) => {
19 console.log(value); // Won't execute
20 })
21 .catch((error) => {
22 console.error(error); // "Error!"
23 });
24
25// Chaining .then() calls
26Promise.resolve(10)
27 .then(value => {
28 console.log(value); // 10
29 return value * 2; // Return value for next .then()
30 })
31 .then(value => {
32 console.log(value); // 20
33 return value + 5;
34 })
35 .then(value => {
36 console.log(value); // 25
37 })
38 .catch(error => {
39 console.error(error); // Catches errors from any .then()
40 });
41
42// .finally() - executes regardless of outcome
43Promise.resolve('Data')
44 .then(data => {
45 console.log(data);
46 return data.toUpperCase();
47 })
48 .finally(() => {
49 console.log('Cleanup: This always runs');
50 });
51
52// Error handling in promise chains
53function fetchData() {
54 return new Promise((resolve, reject) => {
55 setTimeout(() => {
56 const random = Math.random();
57 if (random > 0.5) {
58 resolve('Data received');
59 } else {
60 reject('Network error');
61 }
62 }, 1000);
63 });
64}
65
66fetchData()
67 .then(data => {
68 console.log('Success:', data);
69 return processData(data);
70 })
71 .then(processed => {
72 console.log('Processed:', processed);
73 })
74 .catch(error => {
75 console.error('Error:', error);
76 })
77 .finally(() => {
78 console.log('Request completed');
79 });5. Promise Chaining
One of the most powerful features of Promises is chaining. Since.then() returns a new Promise, you can chain multiple operations together.
How Chaining Works:
- Each
.then()returns a new Promise - The return value of a
.then()callback becomes the resolved value of the next Promise - Errors propagate down the chain until caught by a
.catch() - This allows you to perform sequential asynchronous operations
1// Promise Chaining - Sequential async operations
2
3// Basic chaining
4fetchUser(1)
5 .then(user => {
6 console.log('User:', user);
7 return fetchPosts(user.id); // Return next promise
8 })
9 .then(posts => {
10 console.log('Posts:', posts);
11 return fetchComments(posts[0].id);
12 })
13 .then(comments => {
14 console.log('Comments:', comments);
15 })
16 .catch(error => {
17 console.error('Error in chain:', error);
18 });
19
20// Helper functions
21function fetchUser(userId) {
22 return new Promise((resolve) => {
23 setTimeout(() => {
24 resolve({ id: userId, name: 'John' });
25 }, 500);
26 });
27}
28
29function fetchPosts(userId) {
30 return new Promise((resolve) => {
31 setTimeout(() => {
32 resolve([{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }]);
33 }, 500);
34 });
35}
36
37function fetchComments(postId) {
38 return new Promise((resolve) => {
39 setTimeout(() => {
40 resolve([{ id: 1, text: 'Great post!' }]);
41 }, 500);
42 });
43}
44
45// Chaining with transformations
46Promise.resolve(5)
47 .then(x => {
48 console.log('Step 1:', x);
49 return x * 2;
50 })
51 .then(x => {
52 console.log('Step 2:', x);
53 return x + 10;
54 })
55 .then(x => {
56 console.log('Step 3:', x);
57 return x / 2;
58 })
59 .then(x => {
60 console.log('Final:', x); // 10
61 });
62
63// Error propagation in chains
64Promise.resolve(10)
65 .then(x => {
66 if (x > 5) {
67 throw new Error('Value too large');
68 }
69 return x * 2;
70 })
71 .then(x => {
72 console.log('This won't execute');
73 return x + 5;
74 })
75 .catch(error => {
76 console.error('Caught error:', error.message);
77 return 0; // Return default value
78 })
79 .then(x => {
80 console.log('After catch:', x); // 0
81 });6. Promise.all(), Promise.race(), and Promise.allSettled()
These static methods help you work with multiple Promises:
Promise.all([...promises])
Waits for all promises to resolve. Returns an array of results in the same order.
Fails fast: If any promise rejects, the entirePromise.all() rejects immediately.
Promise.race([...promises])
Returns the first promise that settles (fulfills or rejects).
Use case: Timeout patterns, getting the fastest response.
Promise.allSettled([...promises])
Waits for all promises to settle (fulfill or reject).
Never rejects: Returns results for all promises, regardless of outcome. Useful when you need partial results.
1// Promise.all() - Wait for all promises to resolve
2
3// All promises must succeed
4const promise1 = Promise.resolve(1);
5const promise2 = Promise.resolve(2);
6const promise3 = Promise.resolve(3);
7
8Promise.all([promise1, promise2, promise3])
9 .then(values => {
10 console.log(values); // [1, 2, 3]
11 })
12 .catch(error => {
13 console.error(error); // If any promise rejects
14 });
15
16// Real-world example: Fetching multiple resources
17const fetchUser = fetch('/api/user').then(r => r.json());
18const fetchPosts = fetch('/api/posts').then(r => r.json());
19const fetchComments = fetch('/api/comments').then(r => r.json());
20
21Promise.all([fetchUser, fetchPosts, fetchComments])
22 .then(([user, posts, comments]) => {
23 console.log('User:', user);
24 console.log('Posts:', posts);
25 console.log('Comments:', comments);
26 })
27 .catch(error => {
28 console.error('One or more requests failed:', error);
29 });
30
31// Promise.all() fails fast - if one rejects, all fail
32const p1 = Promise.resolve('Success 1');
33const p2 = Promise.reject('Error 2');
34const p3 = Promise.resolve('Success 3');
35
36Promise.all([p1, p2, p3])
37 .then(values => {
38 console.log(values); // Won't execute
39 })
40 .catch(error => {
41 console.error(error); // "Error 2" - fails immediately
42 });
43
44// Promise.allSettled() - Wait for all, regardless of outcome
45Promise.allSettled([p1, p2, p3])
46 .then(results => {
47 results.forEach((result, index) => {
48 if (result.status === 'fulfilled') {
49 console.log(`Promise ${index + 1} succeeded:`, result.value);
50 } else {
51 console.log(`Promise ${index + 1} failed:`, result.reason);
52 }
53 });
54 });
55
56// Promise.race() - First promise to settle wins
57const fastPromise = new Promise(resolve =>
58 setTimeout(() => resolve('Fast'), 100)
59);
60const slowPromise = new Promise(resolve =>
61 setTimeout(() => resolve('Slow'), 1000)
62);
63
64Promise.race([fastPromise, slowPromise])
65 .then(result => {
66 console.log(result); // "Fast" - first to complete
67 });
68
69// Use case: Timeout pattern
70function fetchWithTimeout(url, timeout = 5000) {
71 const fetchPromise = fetch(url);
72 const timeoutPromise = new Promise((_, reject) =>
73 setTimeout(() => reject(new Error('Timeout')), timeout)
74 );
75
76 return Promise.race([fetchPromise, timeoutPromise]);
77}
78
79fetchWithTimeout('/api/data', 3000)
80 .then(response => response.json())
81 .then(data => console.log(data))
82 .catch(error => {
83 if (error.message === 'Timeout') {
84 console.error('Request timed out');
85 } else {
86 console.error('Request failed:', error);
87 }
88 });7. Error Handling in Promises
Proper error handling is crucial when working with Promises. Errors can occur in several places:
- In the executor function (reject is called)
- In a
.then()callback (throws an error) - In a rejected Promise
Key Points:
- Errors propagate down the chain until caught by a
.catch() - A single
.catch()can handle errors from multiple.then()calls - Always handle promise rejections to avoid "UnhandledPromiseRejectionWarning"
Promise.allSettled()is useful when you want partial results even if some promises fail
1// Error Handling in Promises
2
3// 1. Basic Error Handling
4const promise = new Promise((resolve, reject) => {
5 setTimeout(() => {
6 reject(new Error('Something went wrong'));
7 }, 1000);
8});
9
10promise
11 .then(result => console.log(result))
12 .catch(error => {
13 console.error('Caught error:', error.message);
14 });
15
16// 2. Error in .then() chain
17Promise.resolve(10)
18 .then(value => {
19 throw new Error('Error in then');
20 return value * 2;
21 })
22 .then(value => {
23 console.log('This won't execute');
24 })
25 .catch(error => {
26 console.error('Caught:', error.message);
27 });
28
29// 3. Multiple .catch() handlers
30Promise.reject('First error')
31 .catch(error => {
32 console.log('First catch:', error);
33 throw 'Second error';
34 })
35 .catch(error => {
36 console.log('Second catch:', error);
37 });
38
39// 4. Handling errors in Promise.all()
40const promises = [
41 Promise.resolve('Success 1'),
42 Promise.reject('Error 2'),
43 Promise.resolve('Success 3')
44];
45
46Promise.all(promises)
47 .then(results => {
48 console.log(results); // Won't execute
49 })
50 .catch(error => {
51 console.error('Promise.all failed:', error);
52 });
53
54// 5. Using Promise.allSettled() for partial failures
55Promise.allSettled(promises)
56 .then(results => {
57 results.forEach((result, index) => {
58 if (result.status === 'fulfilled') {
59 console.log(`Promise ${index + 1} succeeded:`, result.value);
60 } else {
61 console.log(`Promise ${index + 1} failed:`, result.reason);
62 }
63 });
64 });
65
66// 6. Error recovery
67function fetchWithFallback(url) {
68 return fetch(url)
69 .then(response => {
70 if (!response.ok) {
71 throw new Error('Network error');
72 }
73 return response.json();
74 })
75 .catch(error => {
76 console.warn('Primary fetch failed, using fallback');
77 return fetch('/api/fallback').then(r => r.json());
78 });
79}
80
81// 7. Try-catch with async/await (for comparison)
82async function handleErrors() {
83 try {
84 const result = await Promise.reject('Error');
85 console.log(result);
86 } catch (error) {
87 console.error('Caught:', error);
88 }
89}
90
91// 8. Unhandled Promise Rejections
92// Always handle promise rejections!
93const unhandled = Promise.reject('Unhandled error');
94// This will cause "UnhandledPromiseRejectionWarning" in Node.js
95
96// Proper handling:
97unhandled.catch(error => {
98 console.error('Handled:', error);
99});8. Advanced Promise Patterns
Once you understand the basics, you can use Promises to solve complex problems:
- Retry Pattern: Automatically retry failed operations
- Sequential Processing: Process items one at a time using
reduce() - Promise Pool: Limit concurrent operations
- Cancellable Promises: Cancel long-running operations
- Converting Callbacks: Wrap callback-based APIs in Promises
1// Advanced Promise Patterns
2
3// 1. Converting Callbacks to Promises
4function readFilePromise(filename) {
5 return new Promise((resolve, reject) => {
6 // Simulating file read
7 setTimeout(() => {
8 if (filename) {
9 resolve(`Content of ${filename}`);
10 } else {
11 reject(new Error('Filename required'));
12 }
13 }, 1000);
14 });
15}
16
17// 2. Retry Pattern
18function retryPromise(fn, maxRetries = 3) {
19 return new Promise((resolve, reject) => {
20 let attempts = 0;
21
22 function attempt() {
23 fn()
24 .then(resolve)
25 .catch(error => {
26 attempts++;
27 if (attempts >= maxRetries) {
28 reject(error);
29 } else {
30 console.log(`Retry attempt ${attempts}`);
31 setTimeout(attempt, 1000);
32 }
33 });
34 }
35
36 attempt();
37 });
38}
39
40// Usage
41retryPromise(() => fetch('/api/data'))
42 .then(data => console.log('Success:', data))
43 .catch(error => console.error('Failed after retries:', error));
44
45// 3. Sequential Processing with reduce
46const urls = ['/api/user', '/api/posts', '/api/comments'];
47
48urls.reduce((promise, url) => {
49 return promise.then(results => {
50 return fetch(url)
51 .then(response => response.json())
52 .then(data => {
53 results.push(data);
54 return results;
55 });
56 });
57}, Promise.resolve([]))
58 .then(allResults => {
59 console.log('All data:', allResults);
60 });
61
62// 4. Promise Pool (Limit Concurrent Promises)
63function promisePool(tasks, limit) {
64 const results = [];
65 let running = 0;
66 let index = 0;
67
68 return new Promise((resolve) => {
69 function runNext() {
70 if (index >= tasks.length && running === 0) {
71 resolve(results);
72 return;
73 }
74
75 while (running < limit && index < tasks.length) {
76 const currentIndex = index++;
77 running++;
78
79 tasks[currentIndex]()
80 .then(result => {
81 results[currentIndex] = result;
82 })
83 .catch(error => {
84 results[currentIndex] = { error };
85 })
86 .finally(() => {
87 running--;
88 runNext();
89 });
90 }
91 }
92
93 runNext();
94 });
95}
96
97// 5. Cancellable Promise
98function cancellablePromise(executor) {
99 let cancel;
100 const promise = new Promise((resolve, reject) => {
101 cancel = () => {
102 reject(new Error('Cancelled'));
103 };
104 executor(resolve, reject);
105 });
106
107 promise.cancel = cancel;
108 return promise;
109}
110
111const task = cancellablePromise((resolve) => {
112 setTimeout(() => resolve('Done'), 5000);
113});
114
115// Cancel after 2 seconds
116setTimeout(() => task.cancel(), 2000);
117
118task
119 .then(result => console.log(result))
120 .catch(error => console.log(error.message)); // "Cancelled"Key Takeaways
- A Promise is an object representing a future value, not the value itself
- Promises have three states: pending, fulfilled, andrejected
- Once settled, a Promise's state cannot change
.then()returns a new Promise, enabling chaining- Errors propagate down the chain until caught by
.catch() Promise.all()fails fast;Promise.allSettled()waits for all- Understanding Promises is essential before using
async/await
💡 Why Learn Promises Before async/await?
async/await is syntactic sugar over Promises. When you use async/await, you're still working with Promises under the hood.
Understanding Promises helps you:
- Debug async/await code more effectively
- Handle errors properly in async functions
- Use Promise methods like
Promise.all()when needed - Understand what's happening behind the scenes