JAVASCRIPT
Implementing a Circuit Breaker Pattern for API Resilience
Build resilient API integrations in JavaScript by implementing a circuit breaker pattern, gracefully handling external service failures and preventing cascading system overloads.
class CircuitBreaker {
constructor(serviceCall, options = {}) {
this.serviceCall = serviceCall; // The function to call the external service
this.failureThreshold = options.failureThreshold || 5; // How many failures before opening
this.resetTimeout = options.resetTimeout || 30000; // How long to stay open (ms)
this.retryTimeout = options.retryTimeout || 10000; // How long in half-open state (ms)
this.failures = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttemptTime = 0;
this.circuitBreakerError = new Error('CircuitBreaker is OPEN');
this.circuitBreakerError.name = 'CircuitBreakerError';
}
async fire(...args) {
if (this.state === 'OPEN') {
if (Date.now() > this.nextAttemptTime) {
this.state = 'HALF_OPEN';
this.nextAttemptTime = Date.now() + this.retryTimeout;
console.log('CircuitBreaker: Moving to HALF_OPEN state.');
} else {
throw this.circuitBreakerError;
}
}
try {
const result = await this.serviceCall(...args);
this.success();
return result;
} catch (error) {
this.fail();
throw error;
}
}
success() {
this.failures = 0;
if (this.state !== 'CLOSED') {
this.state = 'CLOSED';
console.log('CircuitBreaker: Moving to CLOSED state. Service is healthy again.');
}
}
fail() {
this.failures++;
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttemptTime = Date.now() + this.resetTimeout;
console.log(`CircuitBreaker: Moving to OPEN state. Too many failures (${this.failures}).`);
}
}
}
// --- Example Usage ---
async function unreliableService() {
// Simulate an external API call that sometimes fails
const random = Math.random();
if (random < 0.7) { // 70% chance of failure for demonstration
throw new Error('External Service Error!');
}
return { data: 'Service response' };
}
const breaker = new CircuitBreaker(unreliableService, {
failureThreshold: 3,
resetTimeout: 5000, // Stay open for 5 seconds
retryTimeout: 2000 // Try again after 2 seconds in half-open
});
async function testCircuitBreaker() {
for (let i = 0; i < 15; i++) {
try {
const response = await breaker.fire();
console.log(`Attempt ${i + 1}: Success!`, response);
} catch (error) {
console.error(`Attempt ${i + 1}: Error - ${error.message}`);
if (error.name === 'CircuitBreakerError') {
console.log('Circuit is open, skipping call to prevent overload.');
}
}
await new Promise(resolve => setTimeout(resolve, 500)); // Wait a bit between calls
}
}
testCircuitBreaker();
How it works: This JavaScript snippet implements a basic circuit breaker pattern, a critical design pattern for building resilient microservices and integrating with external APIs. When an external service starts failing consistently, the circuit breaker 'opens' (trips), preventing further calls to the failing service and allowing it time to recover, while also protecting your application from being overwhelmed by retries. After a `resetTimeout`, it moves to a 'half-open' state, allowing a few test calls. If these succeed, it 'closes' again; otherwise, it 'opens' once more. This pattern helps prevent cascading failures and improves the overall stability of systems relying on external dependencies.