Circuit Breaker With Half Open State
A typed circuit breaker that trips after consecutive failures, probes recovery in half open state, and resets after a success streak.
Code
export type CircuitState = "closed" | "open" | "half-open";
export interface CircuitBreakerOptions {
failureThreshold: number;
resetTimeoutMs: number;
successThreshold: number;
now?: () => number;
}
export class CircuitBreaker {
private state: CircuitState = "closed";
private failures = 0;
private successes = 0;
private openedAt = 0;
private readonly now: () => number;
private readonly options: CircuitBreakerOptions;
constructor(options: CircuitBreakerOptions) {
this.options = options;
this.now = options.now ?? (() => Date.now());
}
getState(): CircuitState {
if (this.state === "open" && this.now() - this.openedAt >= this.options.resetTimeoutMs) {
this.state = "half-open";
this.successes = 0;
}
return this.state;
}
async call<T>(fn: () => Promise<T>): Promise<T> {
const state = this.getState();
if (state === "open") {
throw new Error("circuit breaker is open");
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (err) {
this.onFailure();
throw err;
}
}
private onSuccess(): void {
this.failures = 0;
if (this.getState() === "half-open") {
this.successes += 1;
if (this.successes >= this.options.successThreshold) {
this.state = "closed";
this.successes = 0;
}
}
}
private onFailure(): void {
this.failures += 1;
if (this.getState() === "half-open") {
this.state = "open";
this.openedAt = this.now();
this.successes = 0;
return;
}
if (this.failures >= this.options.failureThreshold) {
this.state = "open";
this.openedAt = this.now();
}
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.