No Silver Bullet for Async JavaScript

Why this series solves the same async problem seven different ways instead of teaching one 'correct' pattern, and what that actually teaches you.

September 5, 20263 min read1 / 17

Here's the actual idea this post teaches, in one sentence: no async pattern in this series replaces the one before it, each one only fixes one specific complaint the last one couldn't, which is why you end up needing all of them, not just the newest one.

I used to think each new async pattern replaced the last one. Learn promises, forget callbacks. Learn async/await, forget promises. I was wrong every time.

Every Pattern Fixes One Complaint

Here's the plan for this series: one small problem, fetching a few files and doing something with their contents in order, gets solved over and over. First with plain callbacks, then thunks, promises, generators, observables, and finally a pattern called CSP.

Think of it like a toolbox instead of a single gadget. A screwdriver and a drill both drive screws, but nobody says the drill replaces the screwdriver. They just cover different jobs.

  • A callback lets one piece of code hand off to another, once, whenever that second piece is ready. It says nothing about who's allowed to call it, or how many times.
  • A promise fixes that: it guarantees the code you handed control to can only report back once, with exactly one outcome.
  • A generator fixes something promises can't: it lets async code read top to bottom, in order, the way normal code does, pausing instead of nesting.
  • An observable fixes something a promise structurally can't do: represent a stream of many values over time instead of one.
Callbackhands off once
→
Promiseguarantees one outcome
→
Generatorreads top to bottom
→
Observablehandles a stream

That's why this series solves the same problem six times instead of once. Seeing exactly where each tool takes over from the last is the only way to know which one to reach for later, instead of guessing.

Complaint First, Syntax Second

Most explanations of async patterns start with an API: here's .then(), here's yield, here's how you subscribe to a stream. I want to start with the complaint each one was written to fix instead, the same habit I try to use writing about functional JavaScript: figure out what you actually want before learning the syntax for it.

Skip that step, and an API you memorized without its reason turns into a habit you can't explain. You'll write .then() chains long after a plain callback would've been clearer, or reach for a generator to solve something a promise already handles in two lines.

One question is worth carrying through every post here, because it's the same question underneath every async bug you'll ever debug: when, relative to everything else, does this line of code actually run? Callbacks answer that question badly. Everything after them exists to answer it better, in a different way each time.

The next post starts where any async discussion has to start: JavaScript runs on a single thread. Nothing here is happening in parallel, no matter how many things look like they're happening at once.

That one distinction, single thread instead of many, is the ground everything else in this series stands on.