JavaScript Promises: The Complete Guide From Basics to Advanced Patterns

JavaScript Promises: The Complete Guide From Basics to Advanced Patterns

Promises are the foundation of all modern JavaScript async code — and most developers only use 10% of the API. Creating promises, chaining correctly, handling errors without swallowing them, and knowing when to use Promise.all vs allSettled vs race vs any — these distinctions determine correctness and performance in production code.

TL;DR: Promises have three states: pending, fulfilled, rejected. Chain with .then() for success, .catch() for errors. Promise.all fails fast. Promise.allSettled waits for all. Promise.race returns first settled. Promise.any returns first fulfilled. async/await is syntactic sugar over promises.

Creating promises from scratch

// Promise constructor: takes executor (resolve, reject)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

const fetchWithTimeout = (url, timeoutMs) => new Promise((resolve, reject) => {
  const timer = setTimeout(() => reject(new Error('Timeout')), timeoutMs);
  fetch(url)
    .then(res => { clearTimeout(timer); resolve(res); })
    .catch(err => { clearTimeout(timer); reject(err); });
});

// Promisify callback-style functions
const { promisify } = require('util');
const readFile = promisify(require('fs').readFile);
await readFile('file.txt', 'utf8');

Chaining — return value propagation

// Every .then() returns a new Promise
// Return value becomes next .then()'s input
fetch('/api/user')
  .then(res => res.json())           // Returns Promise
  .then(raw => normalize(raw))       // Returns UserNormalized
  .then(user => saveToCache(user))   // Returns Promise
  .catch(err => handleError(err));   // Catches ANY error above

// Mistake: forgetting to return inside .then()
fetch('/api/user')
  .then(res => {
    res.json();  // NO return! Next .then() gets undefined
  })
  .then(data => console.log(data)); // undefined

// Correct:
  .then(res => res.json())  // Implicit return with arrow fn

Promise combinators — all four

const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/settings');

// Promise.all: all succeed or fail together
const [users, posts, settings] = await Promise.all([p1, p2, p3]);
// If p2 rejects: immediately rejects, p1 and p3 results lost

// Promise.allSettled: wait for all, get individual results
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
  if (r.status === 'fulfilled') use(r.value);
  if (r.status === 'rejected')  log(r.reason);
});
// Use when: partial success acceptable (dashboard widgets)

// Promise.race: first to settle wins
const result = await Promise.race([fetch(url), delay(5000).then(() => { throw new Error('timeout'); })]);

// Promise.any: first to FULFILL (ignores rejections)
const fastest = await Promise.any([serverA(), serverB(), serverC()]);

Error handling — the right way

// WRONG: unhandled rejection
async function bad() {
  const data = await riskyCall(); // Throws? Unhandled rejection!
}

// WRONG: catch that swallows
async function alsoBad() {
  try {
    return await riskyCall();
  } catch (e) {} // Error silently swallowed!
}

// RIGHT: handle or re-throw
async function good() {
  try {
    return await riskyCall();
  } catch (e) {
    logger.error('riskyCall failed', e);
    throw e; // Re-throw unless you can meaningfully recover
  }
}

// Result pattern — explicit error as value
async function safe() {
  try { return { ok: true, data: await riskyCall() }; }
  catch (e) { return { ok: false, error: e.message }; }
}
  • ✅ Always return inside .then() callbacks
  • ✅ Promise.allSettled when partial success is acceptable
  • ✅ Promise.race for timeouts and hedged requests
  • ✅ Promise.any for redundant sources — fastest wins
  • ❌ Never swallow errors in .catch() without at minimum logging
  • ❌ Never mix .then()/.catch() chains with async/await in the same function

Promises are the foundation of the async/await guide — async/await is syntax over promises. External reference: MDN Promise documentation.

Recommended Reading

Designing Data-Intensive Applications — The essential book every senior developer needs.

The Pragmatic Programmer — Timeless engineering wisdom for writing better code.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.

✓ No spam✓ Unsubscribe anytime

Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply