FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

JavaScript Promises Deep Dive: How They Work, Chaining, Error Handling and Patterns

Go beyond the basics of JavaScript Promises. Learn how Promises work internally, chaining, error propagation, Promise.all vs Promise.allSettled, and common anti-patterns.

Yusuf SeyitoğluMarch 11, 20263 views9 min read

JavaScript Promises Deep Dive: How They Work, Chaining, Error Handling and Patterns

Most developers know the basics of Promises β€” you create one, call .then(), maybe add a .catch(). But interviews and real production bugs demand a deeper understanding: how chaining actually works, when errors silently disappear, and which utility method to reach for in each situation.

This guide goes beyond the basics.

How Promises Work Internally

A Promise is an object representing the eventual completion or failure of an asynchronous operation. It is always in one of three states:

  • Pending β€” initial state, operation not yet complete
  • Fulfilled β€” operation completed successfully, has a value
  • Rejected β€” operation failed, has a reason (error)

Once a Promise settles (fulfills or rejects), it never changes state again. This is called being immutable once settled.

javascript
const p = new Promise((resolve, reject) => { setTimeout(() => { resolve("done"); // or reject(new Error("failed")) }, 1000); }); console.log(p); // Promise { <pending> } p.then(value => console.log(value)); // "done" after 1 second

The executor function runs synchronously. Only resolve and reject callbacks are async.

Promise Chaining

Every .then() call returns a new Promise. This is what makes chaining work:

javascript
fetch("/api/user/1") .then(response => response.json()) // returns a new Promise .then(user => fetch(`/api/posts?userId=${user.id}`)) // returns a Promise .then(response => response.json()) .then(posts => console.log(posts)) .catch(err => console.error(err));

Each .then() waits for the previous Promise to fulfill before running. If a .then() returns a Promise, the chain waits for that Promise to settle before continuing.

Returning values vs Promises in .then()

javascript
Promise.resolve(1) .then(n => n + 1) // returns 2 (plain value, wrapped in Promise) .then(n => n * 3) // returns 6 .then(n => { return new Promise(resolve => setTimeout(() => resolve(n * 2), 500)); }) // waits 500ms, then continues with 12 .then(n => console.log(n)); // 12

If you return a plain value, it gets wrapped in Promise.resolve(). If you return a Promise, the chain waits for it.

Error Propagation

A rejection skips all .then() handlers until it finds a .catch():

javascript
Promise.resolve("start") .then(v => { throw new Error("something broke"); }) .then(v => console.log("skipped 1")) // skipped .then(v => console.log("skipped 2")) // skipped .catch(err => console.log("caught:", err.message)) // "caught: something broke" .then(v => console.log("continues after catch")); // runs -- catch returns fulfilled Promise

After a .catch() handles an error, the chain resumes as fulfilled unless the .catch() itself throws.

The silent failure trap

The most dangerous Promise mistake β€” an unhandled rejection with no .catch():

javascript
// No .catch() -- error is silently swallowed (or unhandledRejection event) fetchData() .then(process) .then(save); // Always add .catch() fetchData() .then(process) .then(save) .catch(err => { logger.error("Pipeline failed:", err); notifyOncall(err); });

In Node.js, unhandled rejections crash the process in recent versions. Always handle them.

Promise Utility Methods

Promise.all

Runs all Promises concurrently. Resolves when all fulfill. Rejects immediately if any reject:

javascript
const [user, posts, settings] = await Promise.all([ fetchUser(id), fetchPosts(id), fetchSettings(id), ]); // If any one fails, the whole thing fails // Use when: all results are required and failure of one means total failure

Promise.allSettled

Runs all Promises concurrently. Waits for all to settle regardless of outcome:

javascript
const results = await Promise.allSettled([ fetchUser(id), fetchPosts(id), fetchOptionalData(id), ]); results.forEach(result => { if (result.status === "fulfilled") { console.log("Success:", result.value); } else { console.log("Failed:", result.reason.message); } }); // Use when: you want all results, partial success is acceptable

Promise.race

Resolves or rejects with the first Promise to settle:

javascript
// Implement a timeout const withTimeout = (promise, ms) => Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms) ), ]); const result = await withTimeout(fetchData(), 5000);

Promise.any

Resolves with the first fulfilled Promise. Rejects only if all reject (AggregateError):

javascript
// Try multiple sources, use whichever responds first successfully const data = await Promise.any([ fetchFromPrimaryServer(), fetchFromSecondaryServer(), fetchFromCache(), ]); // Use when: any one success is enough

Common Anti-Patterns

The Promise constructor anti-pattern

javascript
// Bad -- unnecessary wrapping of an already-Promise-returning function function getData() { return new Promise((resolve, reject) => { fetch("/api/data") .then(r => r.json()) .then(data => resolve(data)) .catch(err => reject(err)); }); } // Good -- just return the chain function getData() { return fetch("/api/data").then(r => r.json()); }

Forgetting to return in .then()

javascript
// Bug -- the chain does not wait for the inner Promise fetchUser(id) .then(user => { fetchPosts(user.id); // forgot return! }) .then(posts => console.log(posts)); // posts is undefined // Fixed fetchUser(id) .then(user => { return fetchPosts(user.id); // return the Promise }) .then(posts => console.log(posts));

Nested Promises instead of chaining

javascript
// Bad -- callback hell with Promises fetchUser(id).then(user => { fetchPosts(user.id).then(posts => { fetchComments(posts[0].id).then(comments => { console.log(comments); }); }); }); // Good -- flat chain fetchUser(id) .then(user => fetchPosts(user.id)) .then(posts => fetchComments(posts[0].id)) .then(comments => console.log(comments)) .catch(err => console.error(err)); // Better -- async/await const user = await fetchUser(id); const posts = await fetchPosts(user.id); const comments = await fetchComments(posts[0].id);

Promises and async/await

async/await is syntactic sugar over Promises. Every async function returns a Promise:

javascript
async function loadData() { return 42; // equivalent to: return Promise.resolve(42) } loadData().then(console.log); // 42

try/catch in async functions handles Promise rejections:

javascript
async function safeLoad() { try { const data = await fetchData(); return process(data); } catch (err) { console.error("Failed:", err); return null; } }

Practice JavaScript on Froquiz

Promises and async patterns are tested in every serious JavaScript interview. Test your JavaScript knowledge on Froquiz from beginner to advanced.

Summary

  • Promises have three states: pending, fulfilled, rejected β€” immutable once settled
  • Every .then() returns a new Promise β€” this is what enables chaining
  • Rejections skip .then() handlers until a .catch() is found
  • Promise.all β€” all must succeed; Promise.allSettled β€” wait for all regardless
  • Promise.race β€” first to settle wins; Promise.any β€” first to fulfill wins
  • Always add .catch() β€” unhandled rejections crash Node.js processes
  • Return Promises inside .then() or the chain will not wait for them

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • Java Collections Deep Dive: ArrayList, HashMap, TreeMap, LinkedHashMap and When to Use EachMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
All Blogs