Promises in JavaScript: Chaining, Errors, and Runnable Examples

Learn what a Promise contains, predict handler order, connect values through a chain, recover from failures, and choose the right concurrency helper.

KnowledgeGate Team

Exam prep & CS education

Updated 9 Sep 20265 min read

An asynchronous operation gives you a Promise now, but its value or error arrives later. Logging the Promise is not consuming its result. Beginners can use the complete JavaScript learning path. Promises move from pending to one settled outcome, schedule handlers after synchronous work, pass values through chains, and coordinate concurrent operations.

What a JavaScript Promise represents

A Promise represents the eventual completion or failure of an asynchronous operation. It starts pending, then becomes fulfilled with one value or rejected with one reason. Either outcome is settled; later settlement calls cannot change it.

js
const lesson = Promise.resolve({ id: "JS-17", exercises: 3 });
console.log(lesson instanceof Promise); // true
lesson.then(value => console.log(value.id, value.exercises)); // JS-17 3

lesson is the Promise wrapper; value is the fulfilled object delivered later. Promises coordinate browser and server-side JavaScript, but network work is not part of the language. Explore Coding & Skills, or review browser and HTTP context in Web Technologies for Teaching Exams: HTML and HTTP.

Create a Promise and predict its exact execution order

js
console.log("A: before");
const ticket = new Promise(resolve => {
  console.log("B: executor");
  setTimeout(() => resolve("P-17"), 200);
});
ticket.then(value => console.log("D: resolved", value));
console.log("C: after");

The output is:

Code
A: before
B: executor
C: after
D: resolved P-17

The executor runs synchronously. The timer makes resolution eligible after at least 200 ms, not at an exact wall-clock time. The handler follows current synchronous work.

Timeline of the ticket Promise: A before, B executor, and C after print first, then it settles to P-17 and D resolved prints last.

Handle fulfilment, rejection, and cleanup

js
function checkSeats(requested) {
  const available = 3;
  return new Promise((resolve, reject) => {
    if (requested <= available) resolve(`Reserved ${requested} of ${available} seats`);
    else reject(new Error("Only 3 seats available"));
  });
}

Even immediate settlement does not run handlers in the current stack. Run each branch separately:

js
checkSeats(2)
  .then(console.log)
  .catch(error => console.log(error.message))
  .finally(() => console.log("Seat check complete"));
// Reserved 2 of 3 seats
// Seat check complete

checkSeats(5)
  .then(console.log)
  .catch(error => console.log(error.message))
  .finally(() => console.log("Seat check complete"));
// Only 3 seats available
// Seat check complete

.then transforms success, .catch handles earlier rejection, and .finally runs cleanup without receiving or replacing the result unless it throws or returns a rejected Promise. A normal catch return fulfils the chain.

Worked example: chain a two-seat booking from result to total

js
function reserveSeats(requested) {
  const available = 3;
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (requested <= available) {
        resolve({ reservationId: "R-42", seats: requested, farePerSeat: 350 });
      } else {
        reject(new Error("Only 3 seats available"));
      }
    }, 120);
  });
}

reserveSeats(2)
  .then(booking => ({
    ...booking,
    subtotal: booking.seats * booking.farePerSeat
  }))
  .then(booking => Promise.resolve({
    ...booking,
    bookingFee: 35,
    total: booking.subtotal + 35
  }))
  .then(booking => console.log(
    `${booking.reservationId}: ${booking.seats} seats, total ${booking.total}`
  ))
  .catch(error => console.log(`Booking failed: ${error.message}`));

The handlers calculate 2 * 350 = 700, then 700 + 35 = 735, producing R-42: 2 seats, total 735. The 120 ms delay is a teaching simulation. A plain return passes its value; Promise.resolve(...) makes the chain wait. Errors skip success handlers until a catch, keeping the chain flat.

A separate reserveSeats(5) call with the same handlers prints Booking failed: Only 3 seats available.

Booking chain for reserveSeats(2) building subtotal 700 then total 735 and printing R-42, while reserveSeats(5) rejects into the catch.

Coordinate several Promises with the right combinator

Promises start when created, so make fresh sources for every demonstration:

js
function makeSources() {
  return {
    cache: new Promise((_, reject) =>
      setTimeout(() => reject(new Error("cache miss")), 100)),
    api: new Promise(resolve => setTimeout(() => resolve("api data"), 250)),
    backup: new Promise(resolve => setTimeout(() => resolve("backup data"), 150))
  };
}

For each row, call makeSources() and pass [cache, api, backup].

Helper

Completion condition

Exact result

Promise.all

Every input fulfils, or one rejects

Rejects with cache miss

Promise.allSettled

Every input settles

Rejected cache miss, fulfilled api data, fulfilled backup data

Promise.race

First input settles

Rejects with cache miss

Promise.any

First input fulfils

Fulfils with backup data

all and race reject on the 100 ms cache timer. any fulfils on the 150 ms backup timer. allSettled waits through the 250 ms API timer.

Use all for every result, allSettled for a complete report, race for the first settlement, and any for the first success. any rejects with an AggregateError only if all reject. Fulfilled all values preserve input order. Helpers coordinate but do not cancel operations.

Common Promise mistakes and their observable failures

This missing return prints undefined:

js
Promise.resolve(4)
  .then(n => { Promise.resolve(n * 3); })
  .then(console.log);

Return Promise.resolve(n * 3), or use n => Promise.resolve(n * 3), to print 12. Starting an inner Promise does not connect it.

js
new Promise((resolve, reject) => {
  resolve("first");
  reject(new Error("second"));
  resolve("third");
}).then(console.log); // first

A Promise settles once. Do not use repeated settlement as control flow. Use an early return after choosing a branch.

Trap

Observable failure

Repair

.then(showTotal())

Calls showTotal too early

Pass .then(showTotal)

No rejection handler

Can surface an unhandled rejection

Handle it deliberately or return the chain to a caller

catch(error => console.log(error.message))

Next link fulfils with undefined

Log, then throw error if failure must continue

How assessments and interviews test Promises

Common assessments ask you to predict output, trace values, choose a combinator, repair a missing return, or convert a chain to async/await.

  1. Promise.resolve(5).then(n => n + 2).then(n => n * 3).then(console.log) prints 21, because (5 + 2) * 3 = 21.

  2. Promise.reject("E").catch(() => 4).then(console.log) prints 4, because the catch recovers with a fulfilled value.

  3. console.log(1); Promise.resolve().then(() => console.log(3)); console.log(2); prints 1, 2, 3, because the handler waits for synchronous code.

For wider timed preparation, use this Coding Round Strategy for Placements.

Exercise 1: implement loadPrice(quantity) so loadPrice(4).then(price => price + 5) fulfils with 125, but zero rejects.

js
function loadPrice(quantity) {
  if (quantity <= 0) return Promise.reject(new Error("Quantity must be positive"));
  return Promise.resolve(quantity * 30);
}
loadPrice(4).then(price => price + 5).then(console.log);
// 4 * 30 = 120, then 120 + 5 = 125, so it prints 125

Exercise 2: rewrite the successful booking with async/await.

js
async function quoteTotal() {
  const booking = await reserveSeats(2);
  return booking.seats * booking.farePerSeat + 35;
}
quoteTotal().then(console.log); // 2 * 350 + 35 = 735

async function quoteRejected() {
  try { await reserveSeats(5); }
  catch (error) { console.log(error.message); }
}
quoteRejected(); // Only 3 seats available

Promises in JavaScript: the short version and next step

  • A Promise starts pending.

  • It settles once.

  • .then transforms success.

  • .catch handles failure.

  • Returned values and Promises connect chain links.

Retype the R-42 chain and its 700 + 35 = 735 flow. Request three seats, predict subtotal: 1050 and total: 1085, then run it. Request four and predict Only 3 seats available. Explain why no total handler runs.

Beginners can continue with the Complete JavaScript course named above. Later, apply these patterns through the MERN Stack full-stack development course. Predict every transition before running the chain.