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

  1. What is a Promise?
  2. The Three States of a Promise
  3. Creating Promises
  4. Using .then(), .catch(), and .finally()
  5. Promise Chaining
  6. Promise.all(), Promise.race(), and Promise.allSettled()
  7. Error Handling in Promises
  8. 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:

promise-basics.js
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.

promise-states.js
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:

Quick Promise Creation:

4. Using .then(), .catch(), and .finally()

These are the three main methods for handling Promises:

promise-then-catch.js
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:

promise-chaining.js
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.

promise-all.js
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:

Key Points:

promise-error-handling.js
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:

promise-advanced.js
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