CircuitBreaker
import { CircuitBreaker } from "jsr:@ende/async/unstable-circuit-breaker";class · experimental
class CircuitBreaker<T = unknown>A circuit breaker that wraps async operations to prevent cascading failures.
The circuit breaker monitors the failure rate within a sliding window and "trips" (opens) when the rate exceeds a threshold, rejecting subsequent requests immediately without executing them. After a cooldown period, it enters a "half-open" state to test if the service has recovered.
The failure rate is only evaluated once at least
minimumThroughput
requests have been recorded in the window, preventing false positives
during low traffic.
When state changes occur, callbacks fire in a fixed order:
- Circuit trips open:
onFailure→onStateChange→onOpen - Cooldown expires:
onStateChange→onHalfOpen - Recovery completes:
onStateChange→onClose
Example: Usage
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
import { assertEquals } from "@ende/assert";
const breaker = new CircuitBreaker({
failureRateThreshold: 0.5,
minimumThroughput: 20,
cooldownMs: 30000,
});
assertEquals(breaker.state, "closed");
const result = await breaker.execute(() => Promise.resolve("success"));
assertEquals(result, "success");
Example: Handling open circuit
import { CircuitBreaker, CircuitBreakerOpenError } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker();
try {
const result = await breaker.execute(() => fetch("https://api.example.com"));
} catch (error) {
if (error instanceof CircuitBreakerOpenError) {
console.log(`Service unavailable, retry in ${error.remainingCooldownMs}ms`);
}
}
Example: With custom failure detection
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker<Response>({
failureRateThreshold: 0.1,
isResultFailure: (response) => response.status >= 500,
onStateChange: (from, to) => console.log(`Circuit: ${from} → ${to}`),
});
Example: Composing with retry and AbortSignal
import { retry } from "@ende/async/retry";
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker();
// Timeout applies to the entire operation (circuit breaker + retries)
const signal = AbortSignal.timeout(5000);
const result = await breaker.execute(
() => retry(() => fetch("https://api.example.com"), { signal }),
{ signal },
);
Example: Waiting out the cooldown with delay
import { delay } from "@ende/async/delay";
import { CircuitBreaker, CircuitBreakerOpenError } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker();
try {
return await breaker.execute(() => fetch("https://api.example.com"));
} catch (error) {
if (error instanceof CircuitBreakerOpenError) {
// Wait for the circuit to transition to half-open, then retry
await delay(error.remainingCooldownMs);
return await breaker.execute(() => fetch("https://api.example.com"));
}
throw error;
}
Constructors
constructor
new CircuitBreaker(options: CircuitBreakerOptions<T> = [UNSUPPORTED])experimental
Constructs a new CircuitBreaker instance.
options: CircuitBreakerOptions<T> = [UNSUPPORTED]Configuration options for the circuit breaker.
Methods
state
get state(): CircuitStateexperimental
Current state of the circuit breaker.
Time-based transitions (e.g. open to half-open after cooldown) are resolved lazily when this property is read.
Returns
The current CircuitState.
Example: Usage
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
import { assertEquals } from "@ende/assert";
const breaker = new CircuitBreaker();
assertEquals(breaker.state, "closed");
execute
execute<R extends T>(fn: () => R | Promise<R>, options?: CircuitBreakerExecuteOptions): Promise<R>experimental
Executes a function through the circuit breaker.
The function can be synchronous or asynchronous. The result is always returned as a promise.
fn: () => R | Promise<R>The function to execute through the circuit breaker.
options?: CircuitBreakerExecuteOptionsOptions for this execution, such as an abort signal.
Returns
A promise that resolves to the result of the operation.
Example: Usage with async function
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker();
const response = await breaker.execute(() => fetch("https://api.example.com"));
Example: With timeout
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
const breaker = new CircuitBreaker();
// Abort if operation takes longer than 5 seconds
const response = await breaker.execute(
() => fetch("https://api.example.com"),
{ signal: AbortSignal.timeout(5000) },
);
forceOpen
forceOpen(): voidexperimental
Forces the circuit breaker to open state. Useful for maintenance or known outages.
Returns
void
Example: Usage
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
import { assertEquals } from "@ende/assert";
const breaker = new CircuitBreaker();
breaker.forceOpen();
assertEquals(breaker.state, "open");
forceClose
forceClose(): voidexperimental
Forces the circuit breaker to closed state and notifies observers.
This is an operational transition that fires
onStateChange and
onClose callbacks. Use this
when the protected service
has recovered and you want observers to be notified.
For silent resets (e.g., in tests), use reset instead.
Returns
void
Example: Usage
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
import { assertEquals } from "@ende/assert";
const breaker = new CircuitBreaker();
breaker.forceOpen();
breaker.forceClose();
assertEquals(breaker.state, "closed");
reset
reset(): voidexperimental
Silently resets the circuit breaker to initial state.
Unlike forceClose, this does not fire any callbacks
(onStateChange, onClose). Use this for testing or administrative
resets where observers should not be notified.
Returns
void
Example: Usage
import { CircuitBreaker } from "@ende/async/unstable-circuit-breaker";
import { assertEquals } from "@ende/assert";
const breaker = new CircuitBreaker();
breaker.reset();
assertEquals(breaker.state, "closed");