Promise
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It's in one of three states: pending (operation in progress), fulfilled (completed successfully), or rejected (failed).
Explanation
A Promise wraps an asynchronous operation and lets you attach handlers for success (.then(onFulfilled)) and failure (.catch(onRejected)) without the callback-hell nesting. Promises are chainable: .then() returns a new Promise, so you can sequence async operations flatly. The error from any step propagates to the nearest .catch() in the chain, eliminating the need to check errors at every step. Creating a Promise: new Promise((resolve, reject) => { /* async work */ }). Inside, call resolve(value) when done successfully, or reject(error) when it fails. In practice, you rarely construct Promises manually — most modern APIs return them already (fetch, fs.promises, database query methods). Key static methods: Promise.all([p1, p2, p3]) — runs all promises concurrently, resolves when ALL succeed (or rejects if ANY reject), returns array of results. Promise.allSettled([...]) — waits for all to settle regardless of success/failure, returns status+result for each. Promise.race([...]) — resolves/rejects with the first settled promise. Promise.any([...]) — resolves with the first fulfilled promise (ignores rejections). Promises are microtasks — they execute before the next macrotask (setTimeout, setInterval) but after the current synchronous code. This is a subtle but important event loop detail: Promise.resolve().then(() => console.log('micro')) runs before setTimeout(() => console.log('macro'), 0).
Code Example
javascript// Promise fundamentals
// Creating a Promise (usually you don't need to)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Chaining (flat, no nesting)
fetch('/api/user/42')
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json(); // returns another Promise
})
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(res => res.json())
.then(posts => console.log(posts))
.catch(err => console.error('Something failed:', err))
.finally(() => setLoading(false)); // runs whether success or failure
// Concurrent requests: Promise.all
const [user, settings, notifications] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/settings').then(r => r.json()),
fetch('/api/notifications').then(r => r.json()),
]);
// All three fire simultaneously; await until all complete
// Promise.allSettled: when you want all results even if some fail
const results = await Promise.allSettled([p1, p2, p3]);
const successes = results.filter(r => r.status === 'fulfilled');
const failures = results.filter(r => r.status === 'rejected');
Why It Matters for Engineers
Promises are the foundation of modern JavaScript async code. Every fetch call returns a Promise. Every async function returns a Promise. Understanding Promises means understanding how async code actually works — why errors surface where they do, why Promise.all speeds up parallel requests vs sequential awaits, and why unhandled rejections crash Node.js processes. Promise.all vs sequential await is a real performance concern: awaiting three API calls sequentially takes 3x longer than running them in parallel with Promise.all. This is a concrete optimization you can apply in production code once you understand how Promises compose.
Related Terms
Learn This In Practice
Go deeper with the full module on Beyond Vibe Code.
Programming Foundations → →