Skip to content

Circuit Breaker

Prevent cascading failures by tracking step failure rates and short-circuiting calls when a threshold is exceeded.

import { ok } from 'awaitly';
import {
createCircuitBreaker,
isCircuitOpenError,
circuitBreakerPresets,
} from 'awaitly/circuit-breaker';
// Create a circuit breaker (name is required)
const breaker = createCircuitBreaker('external-api', {
failureThreshold: 5, // Open after 5 failures
resetTimeout: 30000, // Try again after 30 seconds
halfOpenMax: 3, // Allow 3 test requests in half-open state
windowSize: 60000, // Count failures within this window (1 minute)
});
// Execute with the breaker
try {
const data = await breaker.execute(async () => {
return await fetchFromExternalApi();
});
console.log('Got data:', data);
} catch (error) {
if (isCircuitOpenError(error)) {
console.log(`Circuit is open, retry after ${error.retryAfterMs}ms`);
} else {
console.log('Operation failed:', error);
}
}
import { createCircuitBreaker, circuitBreakerPresets } from 'awaitly/circuit-breaker';
// For critical services - opens quickly, recovers slowly
const criticalBreaker = createCircuitBreaker(
'payment-api',
circuitBreakerPresets.critical
);
// For lenient services - tolerates more failures
const lenientBreaker = createCircuitBreaker(
'recommendation-api',
circuitBreakerPresets.lenient
);

Use executeResult instead of execute if your operation returns a Result:

const result = await breaker.executeResult(async () => {
return ok(await fetchFromExternalApi());
});
if (!result.ok) {
if (isCircuitOpenError(result.error)) {
console.log('Circuit is open, try again later');
}
}

The circuit breaker has three states:

StateDescription
CLOSEDNormal operation. Failures are tracked.
OPENCircuit tripped. All calls fail immediately.
HALF_OPENTesting recovery. Limited calls allowed.
const stats = breaker.getStats();
console.log(stats.state); // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
console.log(stats.failureCount); // Failures in current window
console.log(stats.successCount); // Successes in current window
console.log(stats.halfOpenSuccesses); // Successful test calls
CLOSED → OPEN: When failureThreshold is reached
OPEN → HALF_OPEN: After resetTimeout elapses
HALF_OPEN → CLOSED: After halfOpenMax successes
HALF_OPEN → OPEN: On any failure
const breaker = createCircuitBreaker('api', circuitBreakerPresets.standard);
const result = await workflow.run(async ({ step }) => {
const data = await step('fetchData', async () => {
const apiResult = await breaker.executeResult(() =>
ok(await externalApi.fetch())
);
return apiResult;
});
return data;
});

Create separate breakers for independent failure domains:

const paymentBreaker = createCircuitBreaker('payment', {
failureThreshold: 3,
resetTimeout: 60000, // Conservative for payments
});
const inventoryBreaker = createCircuitBreaker('inventory', {
failureThreshold: 10,
resetTimeout: 10000, // Can recover faster
});
const emailBreaker = createCircuitBreaker('email', {
failureThreshold: 20,
resetTimeout: 5000, // Non-critical, aggressive recovery
});
{
failureThreshold: number; // Failures before opening (required)
resetTimeout: number; // Ms before half-open (required)
halfOpenMax?: number; // Test calls allowed (default: 1)
windowSize?: number; // Failure counting window (default: 60000)
}