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.

JavaScript
Frontend
3 snippets
circuit-breaker
resilience
fault-tolerance
code-template
vikramross

By @vikramross

March 9, 2026

·

Updated May 20, 2026

407 views

3

4.5 (11)

// CircuitBreaker as a pure state machine. No fetch, no timers.
// Stage 1: prove the transitions. Stage 2 will wrap a real call.

const CLOSED = 'closed';
const OPEN = 'open';
const HALF_OPEN = 'half_open';

function createBreaker({ failureThreshold = 3, openMs = 5000, now = Date.now } = {}) {
    let state = CLOSED;
    let failures = 0;
    let openedAt = 0;

    function onSuccess() {
        failures = 0;
        state = CLOSED;
    }
    function onFailure() {
        failures += 1;
        if (state === HALF_OPEN || failures >= failureThreshold) {
            state = OPEN;
            openedAt = now();
        }
    }
    function canPass() {
        if (state === CLOSED) return true;
        if (state === OPEN && now() - openedAt >= openMs) {
            state = HALF_OPEN;
            return true;
        }
        return state === HALF_OPEN ? false : false;
    }
    return {
        get state() { return state; },
        get failures() { return failures; },
        canPass, onSuccess, onFailure,
    };
}

// Drive the machine with a fake clock so the demo is deterministic.
let t = 0;
const clock = () => t;
const br = createBreaker({ failureThreshold: 2, openMs: 1000, now: clock });

console.log('start state:', br.state);                      // closed
br.onFailure();
console.log('after 1 fail:', br.state, br.failures);         // closed, 1
br.onFailure();
console.log('after 2 fails:', br.state, br.failures);        // open, 2
console.log('canPass while open early:', br.canPass());      // false
t = 1500;                                                     // advance past openMs
console.log('canPass after openMs:', br.canPass());           // true (half_open)
console.log('state during probe:', br.state);                 // half_open
br.onSuccess();
console.log('after probe success:', br.state, br.failures);   // closed, 0

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.