Stop calling a failing dependency for a cooldown period instead of retrying straight into a wall.
If a downstream service is down or overloaded, every caller that keeps hitting it with retries adds load to something already struggling, and each caller's own threads or connections sit blocked waiting on timeouts — the failure spreads instead of staying contained.
Closed lets calls through normally, counting failures. Past a threshold, the breaker trips to open and fails fast without calling the dependency at all. After a cooldown it goes half-open, allows one trial call through, and closes again on success or re-opens on failure.
`call()` refuses to even attempt the request while open, and self-heals by allowing exactly one probe through once the cooldown passes.
class CircuitBreaker {
#state = "closed";
#failures = 0;
#threshold = 3;
#cooldownMs = 5000;
#openedAt = 0;
async call(fn) {
if (this.#state === "open") {
if (Date.now() - this.#openedAt < this.#cooldownMs) {
throw new Error("circuit open — failing fast");
}
this.#state = "half-open";
}
try {
const result = await fn();
this.#failures = 0;
this.#state = "closed";
return result;
} catch (err) {
this.#failures++;
if (this.#failures >= this.#threshold) {
this.#state = "open";
this.#openedAt = Date.now();
}
throw err;
}
}
}
const breaker = new CircuitBreaker();
const flakyCall = () => { throw new Error("service down"); };
for (let i = 0; i < 5; i++) {
breaker.call(flakyCall).catch((e) => console.log(`attempt ${i}: ${e.message}`));
}