Community JavaScript Snippet
A Circuit Breaker State Machine in JavaScript
The 80-line breaker I drop in front of every flaky upstream. Three explicit states, a half-open probe, and a clock you can swap out for tests. No `opossum`, no `cockatiel`, no Redis.
A Circuit Breaker State Machine in JavaScript
The 80-line breaker I drop in front of every flaky upstream. Three explicit states, a half-open probe, and a clock you can swap out for tests. No `opossum`, no `cockatiel`, no Redis.
By @vikramross
March 9, 2026
·
Updated May 20, 2026
407 views
3
4.5 (11)
Pulling the state machine out of the wrapper is the move that makes a circuit breaker testable. Three states, three transitions: CLOSED counts failures and trips OPEN at the threshold, OPEN refuses calls until openMs has passed and then flips to HALF_OPEN, HALF_OPEN lets exactly one probe through and either snaps back to CLOSED on success or back to OPEN on failure. The injectable now clock is the only reason this code is not flaky to test; once you have it you can fast-forward time without jest.useFakeTimers(). Notice that failures resets only on success, not on the OPEN-to-HALF_OPEN transition, so a flapping upstream cannot soft-reset the count by waiting.
The guard factory takes a breaker and returns a callable wrapper. The shape matters: passing the work as await call(() => fetch(url)) keeps the breaker oblivious to what is being protected and lets one breaker guard several call sites that share an upstream. The CircuitOpenError has a stable code field so callers can distinguish a fast-fail (no work was attempted) from a real upstream error (work was attempted and failed). After the threshold is reached, every subsequent call short-circuits without ever invoking flaky, which is the entire point: stop pummelling a failing service while it tries to recover.
The naive half-open lets every queued caller race for the probe slot, which is exactly what you do not want against a recovering database. Adding a probeInFlight flag and gating canPass on it means the first caller wins and the rest get circuit_open immediately. Resetting the flag on both success and failure (and on the OPEN-to-HALF_OPEN transition) handles the case where a probe throws asynchronously. This is the version I ship; the previous one looks correct in unit tests but causes a real-world stampede the moment your retry budget aligns across instances.
