Promises are JavaScript objects that represent the eventual completion or failure of an asynchronous operation. This guide covers everything you need to know about Promises.
What is a Promise? #
A Promise is an object that may produce a single value in the future: either a resolved value or a reason for rejection.
Three States:
- Pending - Initial state
- Fulfilled - Operation completed successfully
- Rejected - Operation failed
Creating Promises #
Basic Promise #
const promise = new Promise((resolve, reject) => {
// Async operation
const success = true;
if (success) {
resolve("Operation successful");
} else {
reject("Operation failed");
}
});Simple Examples #
// Immediately resolved
const resolvedPromise = Promise.resolve("Success!");
// Immediately rejected
const rejectedPromise = Promise.reject("Error!");
// With timeout
const delayedPromise = new Promise((resolve) => {
setTimeout(() => {
resolve("Done after 1 second");
}, 1000);
});Consuming Promises #
then() #
Handle successful resolution:
promise
.then(result => {
console.log(result);
return result;
})
.then(result => {
console.log("Chained:", result);
});catch() #
Handle errors:
promise
.then(result => console.log(result))
.catch(error => console.error(error));finally() #
Runs regardless of outcome:
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log("Cleanup"));Chaining Promises #
fetch('https://api.example.com/user')
.then(response => response.json())
.then(user => fetch(`https://api.example.com/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));Return values:
- If you return a value, next
.then()receives it - If you return a Promise, next
.then()waits for it - If you throw an error, next
.catch()receives it
Promise.resolve(1)
.then(value => {
console.log(value); // 1
return value + 1;
})
.then(value => {
console.log(value); // 2
return value + 1;
})
.then(value => {
console.log(value); // 3
});Promise.all() #
Wait for all promises to resolve:
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [1, 2, 3]
});Use case: Multiple parallel requests:
Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])
.then(responses => Promise.all(responses.map(r => r.json())))
.then(([users, posts, comments]) => {
console.log(users, posts, comments);
})
.catch(error => console.error("One failed:", error));Note: If any promise rejects, Promise.all() rejects immediately.
Promise.allSettled() #
Wait for all promises to settle (resolve or reject):
const promises = [
Promise.resolve(1),
Promise.reject("Error"),
Promise.resolve(3)
];
Promise.allSettled(promises)
.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
Promise.race() #
Returns first promise to settle:
const slow = new Promise(resolve => setTimeout(() => resolve('slow'), 2000));
const fast = new Promise(resolve => setTimeout(() => resolve('fast'), 1000));
Promise.race([slow, fast])
.then(result => console.log(result)); // "fast"
Use case: Timeout for requests:
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
}
Promise.race([
fetch('https://api.example.com/data'),
timeout(5000)
])
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));Promise.any() #
Returns first promise to fulfill:
const promises = [
Promise.reject('Error 1'),
Promise.resolve('Success'),
Promise.reject('Error 2')
];
Promise.any(promises)
.then(result => console.log(result)) // "Success"
.catch(error => console.error(error));Note: Only rejects if all promises reject.
Error Handling #
Basic Error Handling #
promise
.then(result => {
// Handle success
})
.catch(error => {
// Handle error
});Multiple Catch Blocks #
fetch('/api/data')
.then(response => {
if (!response.ok) {
throw new Error('Network error');
}
return response.json();
})
.catch(error => {
console.error('Fetch error:', error);
throw error; // Re-throw if needed
})
.then(data => processData(data))
.catch(error => {
console.error('Processing error:', error);
});Try-Catch in Then #
promise
.then(result => {
try {
return JSON.parse(result);
} catch (error) {
throw new Error('Parse error');
}
})
.catch(error => console.error(error));Practical Examples #
Fetch with Retry #
function fetchWithRetry(url, retries = 3) {
return fetch(url)
.then(response => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.catch(error => {
if (retries > 0) {
console.log(`Retrying... (${retries} left)`);
return fetchWithRetry(url, retries - 1);
}
throw error;
});
}
fetchWithRetry('https://api.example.com/data')
.then(data => console.log(data))
.catch(error => console.error('All retries failed:', error));Delay Function #
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Usage
console.log('Start');
delay(2000)
.then(() => console.log('After 2 seconds'))
.then(() => delay(1000))
.then(() => console.log('After 3 seconds total'));Sequential Promises #
function sequential(promises) {
return promises.reduce((chain, promise) => {
return chain.then(() => promise());
}, Promise.resolve());
}
// Usage
const tasks = [
() => delay(1000).then(() => console.log('Task 1')),
() => delay(1000).then(() => console.log('Task 2')),
() => delay(1000).then(() => console.log('Task 3'))
];
sequential(tasks);
// Logs: Task 1 (after 1s), Task 2 (after 2s), Task 3 (after 3s)
Parallel with Limit #
async function parallelLimit(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
if (limit <= tasks.length) {
const execute = promise.then(() => {
executing.splice(executing.indexOf(execute), 1);
});
executing.push(execute);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}Cache with Promises #
const cache = new Map();
function cachedFetch(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url));
}
return fetch(url)
.then(response => response.json())
.then(data => {
cache.set(url, data);
return data;
});
}Promise Queue #
class PromiseQueue {
constructor() {
this.queue = [];
this.running = false;
}
add(promiseFunction) {
return new Promise((resolve, reject) => {
this.queue.push({ promiseFunction, resolve, reject });
this.run();
});
}
async run() {
if (this.running || this.queue.length === 0) return;
this.running = true;
const { promiseFunction, resolve, reject } = this.queue.shift();
try {
const result = await promiseFunction();
resolve(result);
} catch (error) {
reject(error);
} finally {
this.running = false;
this.run();
}
}
}
// Usage
const queue = new PromiseQueue();
queue.add(() => fetch('/api/data1'));
queue.add(() => fetch('/api/data2'));Promises vs Callbacks #
Callback Hell #
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
console.log(d);
});
});
});
});Promise Chain #
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => getMoreData(c))
.then(d => console.log(d))
.catch(error => console.error(error));Converting Callbacks to Promises #
// Callback-based function
function readFile(filename, callback) {
// ... read file
callback(error, data);
}
// Promisified version
function readFilePromise(filename) {
return new Promise((resolve, reject) => {
readFile(filename, (error, data) => {
if (error) reject(error);
else resolve(data);
});
});
}
// Usage
readFilePromise('file.txt')
.then(data => console.log(data))
.catch(error => console.error(error));Util.promisify (Node.js) #
const util = require('util');
const fs = require('fs');
const readFilePromise = util.promisify(fs.readFile);
readFilePromise('file.txt', 'utf8')
.then(data => console.log(data))
.catch(error => console.error(error));Promises with Async/Await #
// Promise chain
function fetchUser() {
return fetch('/api/user')
.then(response => response.json())
.then(user => fetch(`/api/posts/${user.id}`))
.then(response => response.json())
.then(posts => console.log(posts))
.catch(error => console.error(error));
}
// Async/await
async function fetchUser() {
try {
const response = await fetch('/api/user');
const user = await response.json();
const postsResponse = await fetch(`/api/posts/${user.id}`);
const posts = await postsResponse.json();
console.log(posts);
} catch (error) {
console.error(error);
}
}Best Practices #
- Always handle errors - Use
.catch()or try/catch - Return promises from
.then()for chaining - Don’t nest promises - Use chaining instead
- Use Promise.all() for parallel operations
- Use async/await for cleaner code
- Create helper functions for common patterns
- Test error cases - Don’t just test success
- Use finally() for cleanup
- Avoid promise constructors when possible
- Document async behavior in code comments
Common Mistakes #
- Forgetting to return in
.then() - Not handling rejections (unhandled promise rejection)
- Creating unnecessary promise wrappers
- Mixing callbacks and promises
- Not understanding promise state (resolved ≠ fulfilled)
Promises are fundamental to modern JavaScript. Master them to write clean, maintainable asynchronous code.