What are Promises?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It's a cleaner alternative to callbacks.
🎭 Promise States
// Three states:
// 1. Pending - initial state, not fulfilled or rejected
// 2. Fulfilled - operation completed successfully
// 3. Rejected - operation failed
let promise = new Promise((resolve, reject) => {
// Pending...
// Fulfill
resolve('Success!');
// Or reject
// reject('Error!');
});
// Once settled (fulfilled or rejected), state cannot change
🏗️ Creating Promises
Promise Constructor
// Create a promise
let promise = new Promise((resolve, reject) => {
// Async operation
let success = true;
if (success) {
resolve('Operation succeeded');
} else {
reject('Operation failed');
}
});
// With setTimeout
let delayed = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Done after 2 seconds');
}, 2000);
});
// Immediate resolve
let immediate = Promise.resolve('Already resolved');
// Immediate reject
let failed = Promise.reject('Already rejected');
// Real example: fetch data
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: 'John' });
} else {
reject('Invalid ID');
}
}, 1000);
});
}
Promise Methods
// Promise.resolve - create fulfilled promise
let promise = Promise.resolve(42);
promise.then(value => console.log(value)); // 42
// Promise.reject - create rejected promise
let promise = Promise.reject('Error');
promise.catch(error => console.error(error)); // 'Error'
// Wrap value in promise
let promise = Promise.resolve({ name: 'John' });
// If value is promise, returns it
let p1 = Promise.resolve(42);
let p2 = Promise.resolve(p1);
console.log(p1 === p2); // true
⛓️ Promise Chaining
then() Method
// Handle fulfilled promise
let promise = Promise.resolve(5);
promise.then(value => {
console.log(value); // 5
return value * 2;
}).then(value => {
console.log(value); // 10
return value + 3;
}).then(value => {
console.log(value); // 13
});
// Chain operations
fetchUser(1)
.then(user => {
console.log('User:', user.name);
return fetchPosts(user.id);
})
.then(posts => {
console.log('Posts:', posts.length);
return fetchComments(posts[0].id);
})
.then(comments => {
console.log('Comments:', comments.length);
});
// Return promise from then
fetch('/api/user')
.then(response => response.json()) // Returns promise
.then(user => {
console.log(user);
return fetch(`/api/posts?user=${user.id}`);
})
.then(response => response.json())
.then(posts => console.log(posts));
catch() Method
// Handle rejection
let promise = Promise.reject('Error occurred');
promise.catch(error => {
console.error(error); // 'Error occurred'
});
// Chain with catch
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => {
console.error('Failed:', error);
});
// Catch handles any error in chain
Promise.resolve(5)
.then(value => {
throw new Error('Oops!');
})
.then(value => {
console.log('This will not run');
})
.catch(error => {
console.error('Caught:', error.message); // 'Oops!'
});
// Multiple catch blocks
fetchData()
.then(processData)
.catch(error => {
console.error('Processing failed:', error);
return defaultData; // Recover
})
.then(data => {
console.log('Using data:', data);
})
.catch(error => {
console.error('Fatal error:', error);
});
finally() Method
// Runs regardless of outcome (ES2018)
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
console.log('Cleanup');
hideLoader();
});
// Practical: loading indicator
showLoader();
fetchUser(1)
.then(user => displayUser(user))
.catch(error => showError(error))
.finally(() => hideLoader());
// finally doesn't receive value
promise
.then(value => console.log(value))
.finally(() => {
// No access to value or error
console.log('Done');
});
// Value passes through
Promise.resolve(42)
.finally(() => console.log('Finally'))
.then(value => console.log(value)); // 42
🔀 Promise Combinators
Promise.all()
// Wait for all promises to fulfill
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => {
console.log(values); // [1, 2, 3]
});
// Parallel API calls
let usersPromise = fetch('/api/users').then(r => r.json());
let postsPromise = fetch('/api/posts').then(r => r.json());
let statsPromise = fetch('/api/stats').then(r => r.json());
Promise.all([usersPromise, postsPromise, statsPromise])
.then(([users, posts, stats]) => {
console.log('Users:', users);
console.log('Posts:', posts);
console.log('Stats:', stats);
});
// If any rejects, all() rejects immediately
let p1 = Promise.resolve(1);
let p2 = Promise.reject('Error');
let p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => {
console.log('Success:', values); // Won't run
})
.catch(error => {
console.error('Failed:', error); // 'Error'
});
// All must succeed for all() to succeed
Promise.all([
fetch('/api/user'),
fetch('/api/settings'),
fetch('/api/preferences')
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([user, settings, preferences]) => {
console.log({ user, settings, preferences });
})
.catch(error => {
console.error('One or more requests failed:', error);
});
Promise.race()
// First to settle (fulfill or reject) wins
let p1 = new Promise(resolve => setTimeout(() => resolve(1), 1000));
let p2 = new Promise(resolve => setTimeout(() => resolve(2), 500));
Promise.race([p1, p2])
.then(value => console.log(value)); // 2 (faster)
// Timeout pattern
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => reject('Timeout'), ms);
});
}
Promise.race([
fetch('/api/data'),
timeout(5000)
])
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error)); // 'Timeout' if slow
// First response wins
Promise.race([
fetch('/api/server1/data'),
fetch('/api/server2/data'),
fetch('/api/server3/data')
])
.then(response => response.json())
.then(data => console.log('Got data from fastest server'));
Promise.allSettled()
// Wait for all to settle, never rejects (ES2020)
let p1 = Promise.resolve(1);
let p2 = Promise.reject('Error');
let p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failed:', result.reason);
}
});
});
// Output:
// Success: 1
// Failed: Error
// Success: 3
// Practical: multiple independent operations
let operations = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/invalid') // This fails
];
Promise.allSettled(operations)
.then(results => {
let successful = results.filter(r => r.status === 'fulfilled');
let failed = results.filter(r => r.status === 'rejected');
console.log(`${successful.length} succeeded`);
console.log(`${failed.length} failed`);
return successful.map(r => r.value);
})
.then(responses => Promise.all(responses.map(r => r.json())))
.then(data => console.log('Available data:', data));
Promise.any()
// First to fulfill wins, ignores rejections (ES2021)
let p1 = Promise.reject('Error 1');
let p2 = new Promise(resolve => setTimeout(() => resolve(2), 500));
let p3 = Promise.reject('Error 2');
Promise.any([p1, p2, p3])
.then(value => console.log(value)); // 2
// All reject = AggregateError
Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2')
])
.catch(error => {
console.log(error instanceof AggregateError); // true
console.log(error.errors); // ['Error 1', 'Error 2']
});
// Practical: try multiple mirrors
Promise.any([
fetch('https://mirror1.com/data.json'),
fetch('https://mirror2.com/data.json'),
fetch('https://mirror3.com/data.json')
])
.then(response => response.json())
.then(data => console.log('Got data from a mirror'))
.catch(error => console.error('All mirrors failed'));
💡 Practical Examples
Sequential Operations
// Process items one by one
async function processSequentially(items) {
let results = [];
for (let item of items) {
let result = await processItem(item);
results.push(result);
}
return results;
}
// Or with reduce
function processSequentially(items) {
return items.reduce((promise, item) => {
return promise.then(results => {
return processItem(item).then(result => {
return [...results, result];
});
});
}, Promise.resolve([]));
}
Retry Logic
function retry(fn, maxRetries = 3, delay = 1000) {
return fn().catch(error => {
if (maxRetries <= 0) {
throw error;
}
return new Promise(resolve => {
setTimeout(() => {
resolve(retry(fn, maxRetries - 1, delay));
}, delay);
});
});
}
// Usage
retry(() => fetch('/api/data'), 3, 1000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Failed after retries'));
Timeout Wrapper
function withTimeout(promise, ms) {
let timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
}
// Usage
withTimeout(fetch('/api/data'), 5000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.message === 'Timeout') {
console.error('Request timed out');
} else {
console.error('Request failed:', error);
}
});
Batch Processing
async function batchProcess(items, batchSize = 5) {
let results = [];
for (let i = 0; i < items.length; i += batchSize) {
let batch = items.slice(i, i + batchSize);
let batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
}
return results;
}
// Process 100 items in batches of 10
let items = Array.from({ length: 100 }, (_, i) => i);
batchProcess(items, 10)
.then(results => console.log('All processed:', results.length));
Parallel Limit
async function parallelLimit(tasks, limit) {
let results = [];
let executing = [];
for (let task of tasks) {
let p = Promise.resolve().then(() => task());
results.push(p);
if (limit <= tasks.length) {
let e = p.then(() => {
executing.splice(executing.indexOf(e), 1);
});
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// Max 3 concurrent requests
let tasks = urls.map(url => () => fetch(url));
parallelLimit(tasks, 3)
.then(responses => console.log('All done'));
Cache with Promises
class PromiseCache {
constructor() {
this.cache = new Map();
}
get(key, fn) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
let promise = fn().then(result => {
this.cache.set(key, Promise.resolve(result));
return result;
}).catch(error => {
this.cache.delete(key);
throw error;
});
this.cache.set(key, promise);
return promise;
}
clear() {
this.cache.clear();
}
}
// Usage
let cache = new PromiseCache();
cache.get('user:1', () => fetch('/api/users/1').then(r => r.json()))
.then(user => console.log(user));
// Second call uses cached promise
cache.get('user:1', () => fetch('/api/users/1').then(r => r.json()))
.then(user => console.log('From cache:', user));
Promise Pool
class PromisePool {
constructor(concurrency) {
this.concurrency = concurrency;
this.running = 0;
this.queue = [];
}
async add(fn) {
while (this.running >= this.concurrency) {
await Promise.race(this.queue);
}
this.running++;
let promise = fn().finally(() => {
this.running--;
this.queue.splice(this.queue.indexOf(promise), 1);
});
this.queue.push(promise);
return promise;
}
}
// Usage
let pool = new PromisePool(3); // Max 3 concurrent
for (let i = 0; i < 10; i++) {
pool.add(() => {
console.log('Starting task', i);
return new Promise(resolve => {
setTimeout(() => {
console.log('Completed task', i);
resolve(i);
}, 1000);
});
});
}
⚠️ Common Mistakes
// Forgetting to return promise
function bad() {
fetchData()
.then(data => processData(data)); // Not returned!
}
bad().then(() => {
console.log('Done?'); // Runs immediately, not after fetch
});
function good() {
return fetchData()
.then(data => processData(data));
}
// Nested promises (promise hell)
fetchUser(1)
.then(user => {
fetchPosts(user.id)
.then(posts => {
fetchComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});
// Better: chain
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments));
// Not handling errors
fetchData()
.then(data => processData(data)); // No .catch()!
// Better
fetchData()
.then(data => processData(data))
.catch(error => console.error(error));
// Creating unnecessary promises
function unnecessary() {
return new Promise(resolve => {
fetchData()
.then(data => resolve(data));
});
}
function better() {
return fetchData(); // Already returns promise
}
// Swallowing errors
promise.catch(error => {}); // Don't do this
// At least log
promise.catch(error => console.error(error));
🎯 Key Takeaways
- Promise States: Pending → Fulfilled or Rejected (immutable once settled)
- then(): Handles fulfillment, returns new promise for chaining
- catch(): Handles rejection anywhere in chain
- finally(): Runs cleanup regardless of outcome
- Promise.all(): All must succeed, fails fast on first rejection
- Promise.race(): First to settle wins (fulfill or reject)
- Promise.allSettled(): Waits for all, never rejects
- Always Return: Return promises in then() for proper chaining