Circuit breaker with metrics — track failure rates and latency.
1class CircuitBreaker {2 constructor(options) {3 this.state = "CLOSED";4 this.failureCount = 0;5 this.successCount = 0;6 this.totalRequests = 0;7 this.failureThreshold = options.failureThreshold || 5;8 this.resetTimeout = options.resetTimeout || 30000;9 this.nextAttempt = 0;10 this.latencies = [];11 }1213 async call(fn) {14 if (this.state === "OPEN") {15 if (Date.now() < this.nextAttempt) {16 throw new Error("Circuit breaker is OPEN");17 }18 this.state = "HALF_OPEN";19 }2021 const start = Date.now();22 this.totalRequests++;2324 try {25 const result = await fn();26 const latency = Date.now() - start;27 this.latencies.push(latency);28 if (this.latencies.length > 100) this.latencies.shift();29 this.onSuccess();30 return result;31 } catch (err) {32 this.onFailure();33 throw err;34 }35 }3637 onSuccess() {38 this.successCount++;39 this.failureCount = 0;40 this.state = "CLOSED";41 }4243 onFailure() {44 this.failureCount++;45 if (this.failureCount >= this.failureThreshold) {46 this.state = "OPEN";47 this.nextAttempt = Date.now() + this.resetTimeout;48 }49 }5051 getMetrics() {52 const sorted = [...this.latencies].sort((a, b) => a - b);53 return {54 state: this.state,55 totalRequests: this.totalRequests,56 successCount: this.successCount,57 failureCount: this.failureCount,58 avgLatency: this.latencies.length59 ? this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length60 : 0,61 p95Latency: sorted[Math.floor(sorted.length * 0.95)] || 0,62 };63 }64}6566// Usage67const breaker = new CircuitBreaker({ failureThreshold: 5 });68const result = await breaker.call(() => fetch("https://api.example.com"));69console.log(breaker.getMetrics());