JavaScript Promises: The Complete Guide From Basics to Advanced Patterns

JavaScript Promises: The Complete Guide From Basics to Advanced Patterns
11 views

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. This guide focuses on JavaScript promises, with the production trade-offs senior engineers need to make sound decisions.

⚡ 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 — JavaScript promises

// 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

Related: TypeScript for JavaScript Developers: The Migration Guide That Actually Works · JavaScript Closures, Scope, and the Module Pattern: The Complete Guide · JavaScript Array Methods: Every Method Explained With Real Examples

Related: JavaScript Array Methods: Every Method Explained With Real Examples · AWS DynamoDB: Advanced Patterns for Production at Scale · Design Patterns in JavaScript: The Gang of Four Patterns Every Senior Dev Uses

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


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 comment

No account needed. Name and email are optional.