← Back to list

Four shapes of async Javascript

A huge amount of frontend complexity comes from some objects that all seem to do the same thing: Promise, Observable, Subject, and…

Ayham Al Attar · 2026-06-13 07:24 · 39 claps · 4.8 min read
#asynchronous #async #promises #observables #behaviorsubject
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Four shapes of async Javascript

A huge amount of frontend complexity comes from some objects that all seem to do the same thing: Promise, Observable, Subject, and BehaviorSubject. They all deal with asynchronous data. They all deliver values. They all appear everywhere in modern frontend applications and answer the same question: “How do I handle data that isn’t ready yet?” The difference is how many values, when they start, and who’s listening.

Mental Model

Imagine a pizza shop. Everything you need to know about these four concepts happens inside it:

Promise — one pizza, cooked the moment you order

The oven fires up the instant you order. You get exactly one pizza, once. Even if you never answer the door, it was still made (it’s eager — it runs whether or not you’re listening).

Observable — a meal-plan subscription

Nothing is cooked until you sign up (subscribe). Then pizzas arrive over time, each subscriber gets their own batch, and you can cancel anytime (it's lazy — no subscriber, no cooking).

Subject — the counter loudspeaker

Staff shout “Fresh slices ready!” and everyone in the room hears it at the same moment. Walk in late? You missed that announcement — it’s gone forever.

BehaviorSubject — the menu board at the entrance

Always shows the current special of the day. Anyone who walks in — even hours late — instantly sees the latest special, and it updates live for everyone when it changes.

Keep this pizza shop in mind, every section below maps straight back to it.

Deep Dive

1. Promise — ordering one pizza (eager, single-shot)

The moment you create a Promise, its work begins (it’s eager, the oven is already on). It settles exactly once (resolved or rejected) and then it's frozen forever. You can't cancel it, and you can't get a second value out of it.

Quick note: Promises are built into JavaScript itself. The other three come from RxJS, a reactive programming library used across the frontend ecosystem.

“A Promise delivers one value, once, and starts working immediately.”

const pizza = new Promise((resolve) => {
 console.log(‘Oven is ON immediately!’); // runs right away
 setTimeout(() => resolve(‘🍕’), 2000);
});

pizza.then(p => console.log('Delivered:', p));

2. Observable — the meal-plan subscription (lazy, multi-value, cancellable)

An Observable is just a blueprint, a flyer for the meal plan. Nothing is cooked until .subscribe() is called (it's lazy). It can emit zero, one, or infinite values, and you can stop it anytime with unsubscribe(). Crucially, each subscriber triggers a fresh execution, two subscribers, two separate batches of pizzas (this is called unicast).

“An Observable delivers many values over time, and only starts when someone subscribes.”

import { interval } from ‘rxjs’;

const mealPlan$ = interval(1000); // just a flyer - nothing cooks yet!
const sub = mealPlan$.subscribe(n => console.log('Pizza #', n)); // NOW the kitchen starts

setTimeout(() => sub.unsubscribe(), 5000); // cancel the plan anytime

3. Subject — the counter loudspeaker (manual push, multicast)

A Subject is both an Observable (you can listen to it) and an Observer (you can call .next() to make an announcement). All subscribers share one execution (multicast), one shout, everyone hears it. But there's a catch: walk in late, and past announcements are gone.

“A Subject is an Observable you can push values into manually — and broadcast to many listeners”

import { Subject } from ‘rxjs’;

const loudspeaker$ = new Subject<string>();

loudspeaker$.subscribe(msg => console.log('Alice hears:', msg));
loudspeaker$.next('Fresh slices ready!'); // Alice hears it

loudspeaker$.subscribe(msg => console.log('Bob hears:', msg)); // Bob walks in now
loudspeaker$.next('Last call for garlic bread!'); // both hear this - but Bob missed the first one

4. BehaviorSubject — the menu board (memory included)

A BehaviorSubject requires an initial value (the board is never blank) and always stores the most recent one. Every new subscriber immediately receives the current value, then all future updates. You can even read it synchronously via .getValue().

“A BehaviorSubject is a Subject that remembers its latest value and hands it to anyone who joins late.”

import { BehaviorSubject } from ‘rxjs’;

const menuBoard$ = new BehaviorSubject<string>('Margherita'); // initial special required

menuBoard$.subscribe(s => console.log('Alice sees:', s)); // Alice sees: Margherita (instantly!)
menuBoard$.next('Pepperoni'); // Alice sees: Pepperoni

menuBoard$.subscribe(s => console.log('Bob sees:', s)); // Bob sees: Pepperoni (got the latest!)

📊 Quick Comparison

Promise

  • Pizza-shop role: one pizza order
  • Number of values: exactly 1
  • Starts: immediately (eager)
  • Cancellable: ❌
  • Push values manually: ❌
  • Late subscribers get: the result (always)
  • Initial value: none
  • Cast type: none

Observable:

  • Pizza-shop role: meal-plan subscription
  • Number of values: 0 → ∞
  • Starts: on subscribe (lazy)
  • Cancellable: ✅
  • Push values manually: ❌
  • Late subscribers get: a fresh execution
  • Initial value: none
  • Cast type: unicast

Subject:

  • Pizza-shop role: loudspeaker
  • Number of values: 0 → ∞
  • Starts: when you call .next()
  • Cancellable: ✅
  • Push values manually: ✅ .next()
  • Late subscribers get: nothing past
  • Initial value: none
  • Cast type: multicast

BehaviorSubject:

  • Pizza-shop role: menu board
  • Number of values: 0 → ∞
  • Starts: when you call .next()
  • Cancellable: ✅
  • Push values manually: ✅ .next()
  • Late subscribers get: the latest value
  • Initial value: required
  • Cast type: multicast

Common Mistakes & Pitfalls

1. Forgetting to unsubscribe → memory leaks. Cancel your meal plan when you move out! Long-lived Observables keep running after your component or page is gone. Store the subscription and unsubscribe in your framework’s teardown hook (useEffect cleanup in React, onUnmounted in Vue, ngOnDestroy in Angular):

const sub = mealPlan$.subscribe(render);

// later, when the component unmounts:
sub.unsubscribe(); // ✅ no leak

2. Exposing the Subject itself from a store or service. If you hand out the raw Subject, anyone can grab the loudspeaker and shout nonsense (.next()), corrupting your state. Expose it as a read-only Observable instead:

const _menuBoard = new BehaviorSubject<string>(‘Margherita’); // private - staff only
export const menuBoard$ = _menuBoard.asObservable(); // public - read-only ✅

3. Expecting a Subject to replay old values. The loudspeaker has no memory. If late subscribers need history, use BehaviorSubject (last value) or ReplaySubject (last N values).

4. Thinking Observables run without a subscriber. A meal-plan flyer feeds no one. Creating an Observable does nothing on its own, no subscribe, no execution. This trips up almost every beginner.

When to Use Which?

  • Promise → A truly one-time async operation, especially with async/await. Examples: a fetch() call, reading from a browser API, loading a config file once.
  • Observable → Anything that emits multiple values over time, or where you want operators like map, debounceTime, switchMap. Examples: search-box input, WebSocket messages, scroll/resize events.
  • Subject → You need to manually broadcast events to multiple listeners, and history doesn’t matter. Example: an app-wide event bus (“notification fired”, “modal closed”).
  • BehaviorSubject → You’re modeling shared state that components need right now, not just future changes. Examples: current logged-in user, theme, cart contents. This pattern is the backbone of many lightweight state-management setups.

Wrapping Up

Next time you’re confused, just step back into the pizza shop: a Promise is one pizza order, an Observable is a meal-plan subscription, a Subject is the loudspeaker, and a BehaviorSubject is the menu board at the door. Use Promises for genuine one-shot tasks, Observables for data that flows, Subjects to broadcast events, and BehaviorSubjects for shared state. Master these four, and most reactive frontend code (whatever the framework) will suddenly read like plain English.

Happy streaming! 🚀


메타데이터
post_id
fc89a8c2a40e
slug
four-shapes-of-async-javascript-fc89a8c2a40e
url
https://medium.com/@ayham.alattar/four-shapes-of-async-javascript-fc89a8c2a40e
canonical_url
https://medium.com/@ayham.alattar/four-shapes-of-async-javascript-fc89a8c2a40e
author_url
https://medium.com/@ayham.alattar
status
ok
fetched_at
2026-09-21 03:27:57