JavaScript - Promises

Overview

Estimated time: 35–45 minutes

Promises represent eventual results of asynchronous operations. Learn how to create, chain, and combine promises; handle errors correctly; and understand the microtask queue.

Learning Objectives

  • Create and consume promises with then/catch/finally.
  • Chain promises and propagate values and errors predictably.
  • Use combinators: Promise.all, allSettled, race, and any.
  • Understand the microtask queue and timing with queueMicrotask.

Prerequisites

Creating promises

// A promise that resolves after ms
function delay(ms, value) {
  return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}

delay(100, 'ok').then(v => console.log(v)); // 'ok' after ~100ms

Chaining and error handling

delay(50, 2)
  .then(x => x * 3)       // 6
  .then(x => { if (x > 5) throw new Error('too big'); return x; })
  .catch(err => {
    console.log('caught:', err.message); // 'too big'
    return 0; // recover
  })
  .finally(() => console.log('done'))
  .then(v => console.log('result:', v)); // 0

Microtasks and timing

console.log('A');
Promise.resolve().then(() => console.log('microtask'));
setTimeout(() => console.log('macrotask'), 0);
console.log('B');
// Order: A, B, microtask, macrotask

queueMicrotask(() => console.log('queued microtask'));

Combinators

const a = delay(30, 'A');
const b = delay(60, 'B');

Promise.all([a,b]).then(v => console.log('all:', v));            // ['A','B'] (after ~60ms)
Promise.allSettled([a, Promise.reject('X')]).then(console.log);
Promise.race([a,b]).then(v => console.log('race:', v));          // 'A' (first to settle)
Promise.any([Promise.reject('e1'), b]).then(v => console.log('any:', v)); // 'B'

Wrapping callback APIs

// Convert a Node-style callback function to a promise-based one
function toPromise(fn, ...args) {
  return new Promise((resolve, reject) => {
    fn(...args, (err, res) => err ? reject(err) : resolve(res));
  });
}

// Example using setTimeout as a fake callback API
function cbTimeout(ms, cb) { setTimeout(() => cb(null, ms), ms); }

toPromise(cbTimeout, 20).then(v => console.log('cb -> promise:', v));

Common Pitfalls

  • Missing return: Always return a value or promise in then handlers to chain correctly.
  • Throw vs reject: In then, throw turns into a rejection; use whichever reads better.
  • Executor errors: Exceptions inside new Promise(executor) immediately reject.
  • Swallowed errors: End a chain with catch or .catch(console.error) to avoid unhandled rejections.

Try it

Run to see chaining, errors, and combinators in action: