jypi
  • Explore
ChatWays to LearnMind mapAbout

jypi

  • About Us
  • Our Mission
  • Team
  • Careers

Resources

  • Ways to Learn
  • Mind map
  • Blog
  • Help Center
  • Community Guidelines
  • Contributor Guide

Legal

  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Content Policy

Connect

  • Twitter
  • Discord
  • Instagram
  • Contact Us
jypi

© 2026 jypi. All rights reserved.

CS50 - Web Programming with Python and JavaScript
Chapters

1Orientation and Web Foundations

2Tools, Workflow, and Git

3HTML5 and Semantic Structure

4CSS3, Layouts, and Responsive Design

5Python Fundamentals for the Web

6Flask, Routing, and Templates

7Data, SQL, and ORM Patterns

8State, Sessions, and Authentication

9JavaScript Essentials and the DOM

10Asynchronous JS, APIs, and JSON

Fetch API fundamentalsPromises chaining and errorsAsync await patternsJSON parsing and schemasCORS and preflight requestsRESTful API design basicsWebSockets and realtimeServer Sent Events overviewFormData and uploadsDebouncing and throttlingPagination and infinite scrollCaching and ETagsRate limits and retriesClient side routing basicsProgressive web app concepts

11Frontend Components and React Basics

12Testing, Security, and Deployment

Courses/CS50 - Web Programming with Python and JavaScript/Asynchronous JS, APIs, and JSON

Asynchronous JS, APIs, and JSON

26875 views

Fetch and process data asynchronously, design and consume APIs, and add realtime features.

Content

2 of 15

Promises chaining and errors

Promises Chaining and Errors in JavaScript — Clear Guide
4396 views
beginner
humorous
web-development
javascript
gpt-5-mini
4396 views

Versions:

Promises Chaining and Errors in JavaScript — Clear Guide

Watch & Learn

AI-discovered learning video

Sign in to watch the learning video for this topic.

Sign inSign up free

Start learning for free

Sign up to save progress, unlock study materials, and track your learning.

  • Bookmark content and pick up later
  • AI-generated study materials
  • Flashcards, timelines, and more
  • Progress tracking and certificates

Free to join · No credit card required

Promises chaining and errors — JavaScript guide for CS50 Web

Ever had a fetch call quietly fail while your UI smugly showed a spinner forever? Welcome to the emotional rollercoaster of Promise chaining and error handling.

You already know how to use the Fetch API from the previous topic, and you've played with template literals and destructuring to make your code readable. Now we take those building blocks and learn how promises talk to each other — and how to politely (or ruthlessly) handle when one of them throws a tantrum.


What this lesson covers

  • How promise chaining really works (not just the eye candy .then().then())
  • How errors propagate through chains and common pitfalls that make errors disappear like socks in a laundry machine
  • Practical patterns using fetch, destructuring, and even custom Error classes
  • Quick conversions to async/await — because sometimes the prose needs to be less poetic

This is a progression from DOM interaction and Fetch basics — now we focus on control flow: what happens when multiple async steps depend on each other.


Quick refresher (one-liner)

  • A .then() callback that returns a value becomes a resolved promise with that value.
  • A .then() callback that returns a promise waits for it — that’s the chaining magic.
  • Throwing inside .then() or returning Promise.reject(...) results in a rejection that travels down the chain until a .catch() is found.

Micro explanation

Think of each .then() as a conveyor belt station: pass a product (value) on, or throw it off (error). If you launch a new conveyor (return a promise), the next station waits for that new conveyor to finish.


Chaining example with fetch and destructuring

// Get user -> get profile -> log profile, robustly handling errors
fetch('/api/users/1')
  .then(response => {
    if (!response.ok) throw new Error(`HTTP ${response.status}`); // stop chain
    return response.json(); // returns a promise that resolves with parsed JSON
  })
  .then(({ username }) => fetch(`/api/profiles/${username}`)) // return the next promise
  .then(profileRes => {
    if (!profileRes.ok) throw new Error('Profile fetch failed');
    return profileRes.json();
  })
  .then(profile => console.log('Profile:', profile))
  .catch(err => console.error('Chain failed:', err));

Notes:

  • We used destructuring in .then(({ username }) => ...) (remember that from the previous module).
  • We return the fetch for the profile so the chain waits for it. Not returning is a common bug.

Common mistakes and how to fix them

  1. Not returning a promise inside .then
// Bad: inner fetch result is lost
fetch('/api/users/1')
  .then(res => res.json())
  .then(user => { fetch(`/api/profile/${user.id}`); }) // forgot to return
  .then(profile => console.log(profile)) // profile is undefined
  .catch(err => console.error(err));

Fix: return the inner promise.

  1. Swallowing errors by placing .catch() too soon
fetch('/a')
  .then(res => res.json())
  .catch(err => { console.warn('Fetch failed, but continuing'); })
  .then(data => doSomething(data)); // data might be undefined

If the .catch() doesn't rethrow or return a fallback, the chain continues with undefined. That can produce subtle bugs. If you intend to recover, return a fallback; otherwise rethrow.

  1. Nested .then() (anti-pattern)

Nesting leads to spaghetti logic. Prefer returning promises and chaining.


Error propagation: throw vs return Promise.reject()

  • throw new Error('x') inside .then() is identical to return Promise.reject(new Error('x')) in terms of propagation.
  • Any thrown error or rejection will skip downstream .then() handlers until it finds a .catch().

Example of recovery mid-chain:

fetch('/maybe')
  .then(res => { if (!res.ok) throw new Error('nope'); return res.json(); })
  .catch(err => {
    console.warn('Recovering from error, providing default');
    return { default: true }; // chain continues with this value
  })
  .then(data => console.log('Data (maybe fallback):', data));

This pattern is useful when you want to recover and continue; otherwise rethrow to let a later .catch() handle it.


Advanced tip: custom Error classes and instanceof

If you build larger apps, define custom errors so your .catch() can handle different problems differently.

class APIError extends Error {}

fetch('/api')
  .then(res => { if (!res.ok) throw new APIError('API down'); return res.json(); })
  .catch(err => {
    if (err instanceof APIError) { /* handle API error */ }
    else { throw err; } // rethrow unknown errors
  });

This builds on your knowledge of JS classes and inheritance (remember that lecture!).


Async/await: same mechanics, different clothes

Promises chaining is the engine; async/await is a nicer dashboard.

async function loadProfile() {
  try {
    const res = await fetch('/api/users/1');
    if (!res.ok) throw new Error('User fetch failed');
    const { username } = await res.json();
    const profileRes = await fetch(`/api/profiles/${username}`);
    if (!profileRes.ok) throw new Error('Profile fetch failed');
    const profile = await profileRes.json();
    console.log(profile);
  } catch (err) {
    console.error('Error:', err);
  }
}

Errors thrown inside async functions behave like rejected promises and are caught by the catch block. Same propagation semantics, cleaner syntax.


Quick debugging checklist

  • Use the Network tab: is the request sent? what's the status?
  • Check response.ok before parsing JSON — trying to parse an HTML error page as JSON is a classic facepalm.
  • Add console.error(err) in .catch() or catch blocks — don't silently ignore.
  • Listen for unhandled rejections in dev:
window.addEventListener('unhandledrejection', e => console.error('Unhandled rejection:', e.reason));

Key takeaways

  • Always return promises from .then() when you want to chain async work.
  • Throw or return Promise.reject to signal errors; they propagate until a .catch.
  • Use .catch() at the right place — either to recover (and return a fallback) or to handle terminal errors.
  • Async/await makes chains look synchronous but follows the same propagation rules.
  • Use custom Error classes (remember classes & inheritance) for clearer error handling.

Here's the mental image: Promise chains are a relay race. If a runner trips (throws), the baton (value) doesn't magically appear at the finish line — someone must catch it, dust it off, and keep running, or call the medics (your .catch).


Remember: Promises don't just make async code possible — they let you compose logic cleanly. Respect the return, don't swallow errors, and your UI will stop ghosting your users.

Happy chaining. Go break things gracefully.

Flashcards
Mind Map
Speed Challenge

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Ready to practice?

Sign up now to study with flashcards, practice questions, and more — and track your progress on this topic.

Study with flashcards, timelines, and more
Earn certificates for completed courses
Bookmark content for later reference
Track your progress across all topics