DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk5 min

JavaScript Promises: States, Chaining, Callback Hell and Errors

A practical guide to promise states, chaining, callback hell, missing returns, error propagation, and when JavaScript promise handlers run.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Promises make asynchronous JavaScript easier to sequence and reason about: each step returns a new promise, and the value or error it produces flows to the next step. The key is to return asynchronous work from each handler. If you leave it out, the chain cannot wait for that work or pass its result onward.

What a promise represents

A promise is an object representing the eventual outcome of an asynchronous operation. It begins pending, then settles as either fulfilled or rejected. Fulfilled and rejected are the two settled states; once settled, a promise cannot change to the other outcome.

As an Amazon Associate I earn from qualifying purchases.

“Resolved” is not always interchangeable with “fulfilled.” Resolving a promise with a promise-like object, or thenable, makes it adopt that object’s eventual state. For example, Promise.resolve(value) follows a thenable rather than simply fulfilling with the thenable as an ordinary value. See the MDN Promise reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Promise constructor takes an executor function that receives resolve and reject. It is useful for adapting work that must explicitly signal success or failure, such as a callback-based API. Most application code should begin with an API that already returns a promise rather than wrapping it in a new constructor. MDN’s guide to using promises describes this as the more common pattern.

How promises flatten callback hell

Callback hell, sometimes called the “pyramid of doom,” is deeply nested code in which each asynchronous callback starts the next operation. The nesting can make the order of work and the path for errors harder to follow. A promise chain lets dependent operations appear one after another at the same indentation level. It changes the structure of the code, not the asynchronous nature of the work.

The following schematic examples use illustrative function names, not a particular API:

// Nested callback shape
getUser(id, (user) => {
  getOrders(user, (orders) => {
    getTotal(orders, (total) => showTotal(total));
  });
});

// Promise chain shape
getUser(id)
  .then((user) => getOrders(user))
  .then((orders) => getTotal(orders))
  .then((total) => showTotal(total))
  .catch(showError);

The chain makes the sequence visible without nesting each next step inside the previous callback. Promise reactions also provide a consistent path for forwarding failures, as described in MDN’s promise guide. A peer-reviewed analysis by Madsen, Lhoták, and Tip likewise examines how promise reactions allow sequential-looking code at one level: A Model for Reasoning About JavaScript Promises.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What happens in a promise chain

.then(onFulfilled, onRejected) attaches handlers and returns a different promise. That new promise represents the handler’s completion and adopts its outcome. In practical terms, every handler determines what the next link receives:

  • If a handler returns an ordinary value, the next promise fulfills with that value.
  • If it returns another promise or thenable, the next promise follows that operation’s eventual outcome.
  • If there is no handler for the current settlement, that outcome passes through the link unchanged.

For example, each handler below passes a result to the following handler:

fetchUser(id)
  .then((user) => fetchOrders(user.id))
  .then((orders) => orders.length)
  .then((count) => console.log(count));

The first handler returns fetchOrders(...), so the chain waits for it and passes its fulfilled result—orders—to the next handler. Returning orders.length then makes that number the input to the final handler.

Why a missing return can produce undefined

Consider a handler that starts a promise but does not return it:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
fetchUser(id)
  .then((user) => {
    fetchOrders(user.id); // started, but not returned
  })
  .then((orders) => console.log(orders));

The first handler completes without returning a value, so its dependent promise fulfills with undefined. The next handler does not receive the orders: the separately started fetchOrders operation is not connected to the chain, and the chain does not wait for it. This disconnected work is often called a floating promise. Return the promise to keep the work and its result in the chain:

fetchUser(id)
  .then((user) => {
    return fetchOrders(user.id);
  })
  .then((orders) => console.log(orders));

With an expression-bodied arrow function, the return is implicit: .then((user) => fetchOrders(user.id)). This return behavior is a common source of unexpected undefined values; MDN discusses it in its guide to promise chaining.

How rejection and exceptions move through a chain

A rejection skips fulfillment-only handlers and moves down the chain until a rejection handler is found. An exception thrown inside a .then() handler rejects the promise returned by that .then(), so a later .catch() can handle it. The same general rule applies to failures from returned promises: their rejection becomes the chain’s rejection.

A .catch(handler) is a rejection handler that returns another promise. If its callback finishes normally, that returned promise fulfills with the callback’s return value—or with undefined if the callback returns nothing. In other words, catching an error and returning normally recovers the chain; it does not keep an error flag attached. To log an error while preserving the failure for a caller, throw it again or return a rejected promise:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
loadData()
  .then(transform)
  .catch((error) => {
    report(error);
    throw error; // preserve the rejection for the next caller
  });

A catch at the end is useful when failures share the same handling. Put a catch nearer to an operation when that operation is optional and a fallback should let the main chain continue. The important distinction is the handler’s outcome: return normally to recover, or throw/return a rejection to continue propagating failure. MDN explains these propagation and recovery rules in its promise guide and Promise reference.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When promise handlers run

The executor passed to new Promise(...) runs as part of constructing the promise. By contrast, handlers registered with .then() do not run synchronously in the current call stack—even if the promise is already fulfilled. They are scheduled as microtasks, which run after the current JavaScript stack completes.

Promise.resolve().then(() => console.log("later"));
console.log("now");

This prints now before later. Promises describe and sequence asynchronous outcomes; they do not make CPU-heavy JavaScript run in parallel. MDN covers reaction scheduling in its guide to using promises.

A practical debugging checklist

  • The next handler receives undefined: check whether the preceding handler returns its value or promise. A block-bodied arrow function needs an explicit return.
  • A later step runs before an operation finishes: check whether that operation’s promise is returned from the handler. If it is not, the work is floating outside the chain.
  • A fulfillment handler is skipped: trace the chain for a rejection or thrown exception and find the next rejection handler.
  • The chain continues after a catch: inspect the catch callback’s result. A normal return recovers; a throw or returned rejection keeps the chain rejected.
  • Output appears after a synchronous log: that is expected for a .then() reaction, which runs as a microtask after the current stack.

These checks follow the promise value and error through each returned promise, rather than treating .then() as a command that merely runs a callback.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.