JSInterview30-Part 5: Implementing a Circuit Breaker in JavaScript

Saurabh Mhatre
5 min readMar 25, 2024

--

Title image

Introduction:

This is a popular question asked interview rounds of Atlassian.
In the realm of distributed systems and microservices architecture, ensuring the resilience of our applications against failures is paramount. One powerful pattern for achieving this resilience is the implementation of a Circuit Breaker. Inspired by its namesake in electrical engineering, a Circuit Breaker in software serves to prevent cascading failures and improve the overall stability of our systems. In this article, we’ll explore how to implement a Circuit Breaker in JavaScript, enabling our applications to gracefully handle faults and recover from them.

Steps:

Understanding the Circuit Breaker Pattern: Before diving into implementation, it's essential to grasp the core concepts behind the Circuit Breaker pattern. Essentially, a Circuit Breaker monitors for failures and, after a predefined threshold, opens the circuit, redirecting calls to a fallback mechanism.

Defining Failure Thresholds: Determine the criteria that trigger the Circuit Breaker to open. This could include the number of consecutive failures, response timeouts, or any other relevant metrics specific to your application's requirements.

Implementing the Circuit Breaker Logic: Write the logic for the Circuit Breaker, including the monitoring of calls, tracking failures, and transitioning between states (Closed, Open, and Half-Open) based on the defined thresholds.

Here’s a brief explanation of each state in the context of the Circuit Breaker pattern:

1. Closed State
- In the Closed state, the Circuit Breaker allows normal operation and permits requests to pass through to the underlying system or service.
- During this state, the Circuit Breaker monitors the responses from the system or service. If the responses are successful and within expected thresholds, the Circuit Breaker remains in the Closed state.
- The Closed state indicates that the system or service is functioning correctly, and there are no ongoing issues detected.

2. Open State:
- In the Open state, the Circuit Breaker detects that the underlying system or service is experiencing issues or failures.
- When the Circuit Breaker transitions to the Open state, it prevents requests from passing through to the system or service. Instead, it redirects requests to a predefined fallback mechanism.
- The Open state serves as a protective measure to prevent further stress on the failing system or service, helping to avoid cascading failures and preserving system stability.
- The Circuit Breaker typically remains in the Open state for a predefined period or until certain conditions are met, such as a timeout or a specified number of failed attempts.

3. Half-Open State:
- The Half-Open state represents a transitional phase for the Circuit Breaker, allowing a limited number of requests to pass through to the underlying system or service to determine if the issues have been resolved.
- After a certain timeout period in the Open state, the Circuit Breaker transitions to the Half-Open state, indicating that it’s testing the system or service’s readiness to handle requests again.
- During the Half-Open state, if the test requests succeed, the Circuit Breaker transitions back to the Closed state, indicating that the system or service has recovered.
- However, if the test requests fail, the Circuit Breaker returns to the Open state, indicating that the underlying issues persist, and further action may be required.

These states play a crucial role in the resilience of distributed systems by providing mechanisms to handle failures, prevent system overload, and maintain overall stability.

Fallback Mechanism: Establish a fallback mechanism to handle requests when the Circuit Breaker is open. This could involve returning cached data, providing default values, or redirecting requests to alternative services.

Implementation:

class CircuitBreaker {
constructor(threshold, timeout) {
this.threshold = threshold;
this.timeout = timeout;
this.failureCount = 0;
this.state = 'closed';
}

async callService() {
if (this.state === 'open') {
return this.fallback();
}

try {
// Call the external service here
// If successful, reset failureCount
// If unsuccessful, increment failureCount
} catch (error) {
this.handleFailure();
return this.fallback();
}
}

handleFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.open();
setTimeout(() => this.halfOpen(), this.timeout);
}
}

open() {
this.state = 'open';
console.log('Circuit Breaker opened');
}

halfOpen() {
this.state = 'half-open';
console.log('Circuit Breaker half-opened');
}

close() {
this.state = 'closed';
this.failureCount = 0;
console.log('Circuit Breaker closed');
}

fallback() {
// Fallback mechanism implementation
}
}

// Example usage:
const circuitBreaker = new CircuitBreaker(3, 5000);
for (let i = 0; i < 10; i++) {
circuitBreaker.callService()
.then(response => console.log('Response:', response))
.catch(error => console.error('Error:', error));
}

Code Explanation

Let’s break down the provided JavaScript code snippet, along with an example usage scenario:

1. CircuitBreaker Class Definition:
- The `CircuitBreaker` class is defined to encapsulate the logic for implementing the Circuit Breaker pattern.
- It takes two parameters: `threshold` (the number of consecutive failures before the circuit opens) and `timeout` (the time to wait before attempting to half-open the circuit after it opens).

2. callService Method:
- This method simulates calling an external service.
- If the circuit is open, it immediately triggers the fallback mechanism.
- Otherwise, it tries to call the external service. If successful, it resets the failure count. If unsuccessful (throws an error), it increments the failure count and triggers the fallback mechanism.

3. handleFailure Method:
- Increments the failure count and opens the circuit if the failure count reaches the defined threshold.
- After opening the circuit, it schedules a timeout to attempt to half-open the circuit after the specified timeout period.

4. open, halfOpen, and close Methods:
- These methods manage the state of the circuit breaker.
- `open`: Sets the state to 'open' and logs a message indicating that the circuit breaker is opened.
- `halfOpen`: Sets the state to 'half-open' and logs a message indicating that the circuit breaker is half-opened. This state allows a limited number of requests to test if the underlying problem has been resolved.
- `close`: Sets the state to 'closed' and resets the failure count. It indicates that the circuit breaker has successfully handled the failure and is back to normal operation.

5. fallback Method:
- This method represents the fallback mechanism to handle requests when the circuit is open. It can be implemented to return cached data, provide default values, or redirect requests to alternative services.

6. Example Usage:
- Instantiates a `CircuitBreaker` object with a threshold of 3 failures and a timeout of 5000 milliseconds.
- Calls the `callService` method of the circuit breaker in a loop to simulate multiple service calls.
- Each call is asynchronous and returns a promise. Depending on the result (success or failure), it logs the response or error.

This implementation ensures that the circuit breaker monitors the number of failures, opens the circuit when the threshold is reached, transitions to a half-open state after a timeout, and closes the circuit once the underlying issue is resolved. It provides a resilient mechanism to handle failures in external services and promotes the stability of the application.

Live demo: Codepen

If you check the output in Codepen above you will notice that error is thrown thrice in the callService method for which fallback is invoked. On fourth iteration, circuit opens and fallback is again invoked for rest of the iterations to handle failure.

The output of for loop is printed later since it is invoked after internal running of circuit breaker is completed due to underlying mechanism of event loop.

Conclusion:

By implementing a Circuit Breaker in JavaScript, we enhance the resilience of our applications by mitigating the impact of failures in external services or dependencies. This proactive approach to fault tolerance promotes system stability, improves user experience, and ultimately contributes to the reliability of our software solutions. Incorporating such patterns into our development practices is crucial for building robust, scalable, and resilient applications in today’s dynamic and interconnected digital landscape.

--

--