Failing fast
When a dependency is down, waiting for each request to time out ties up your threads and spreads the outage. A circuit breaker counts failures; after too many it opens and rejects calls instantly. After a cooldown it goes half-open and lets one trial call through: success closes it, failure opens it again.
Three states
function createBreaker(fn, { threshold = 3, cooldown = 1000, now = () => Date.now() } = {}) {
let state = "closed";
let failures = 0;
let openedAt = 0;
return {
async call(...args) {
if (state === "open") {
if (now() - openedAt < cooldown) throw new Error("Circuit open"); // fail fast
state = "half-open"; // let one trial request through
}
try {
const value = await fn(...args);
state = "closed";
failures = 0;
return value;
} catch (e) {
failures++;
if (state === "half-open" || failures >= threshold) {
state = "open";
openedAt = now();
}
throw e;
}
},
state: () => state,
};
}