Info

A Pattern of Error Handling where after too many failed attempts to perform an action, attempts will be stopped altogether and instead a cached or default response will be returned.

Example

class CircuitBreaker {
    constructor({failureThreshold=3, successThreshold=2, timeout=5000}={}) {
        this.failureThreshold = failureThreshold;
        this.successThreshold = successThreshold;
        this.timeout = timeout;
        this.state = 'CLOSED';
        this.failureCount = 0;
        this.successCount = 0;
    }
 
    async call(fn) {
        if (this.state === 'OPEN') {
            return this.handleOpenState();
        }
 
        try {
            const res = await fn();
            this.successCount++;
            if (this.successCount >= this.successThreshold) {
                this.successCount = 0;
                this.failureCount = 0;
                this.state = 'CLOSED';
            }
            return res;
        } catch (e) {
            this.failureCount++;
            if (this.failureCount >= this.failureThreshold) {
                this.state = 'OPEN';
                setTimeout(() => {
                    this.state = 'HALF_OPEN';
                }, this.timeout);
            }
            throw e;
        }
    }
 
    handleOpenState() {
        throw new Error('Circuit is open');
    }
}
enum CircuitStates {
	OPEN,
	HALF_OPEN,
	CLOSED
}
 
class CircuitBreaker {
	private counters = new Map();
	constructor(
		private readonly failureTreshold = 5,
		private readonly refillTimeMs = 1111
	) { }
 
	call(fn: () => any) {
		const counter = this.createEmptyOrReturn(fn);
		const now = Date.now();
 
		const refillCount = Math.floor((now - counter.lastRefillTime) / this.refillTimeMs);
 
		if (refillCount > 0) {
			console.log(`Refilling with: ${refillCount}`);
			console.log(`now: ${now}, lastCallTime: ${counter.lastRefillTime}`);
 
			counter.lastRefillTime = Date.now();
			counter.tokens += refillCount;
		}
 
		if (counter.tokens == 0) {
			throw new Error("Handle me");
		}
 
		try {
			fn();
		} catch (e) {
			counter.tokens--;
		}
	}
 
	createEmptyOrReturn(fn: () => any): { tokens: number, lastRefillTime: number; } {
		if (!this.counters.has(fn)) {
			this.counters.set(fn, {
				tokens: this.failureTreshold,
				lastRefillTime: Date.now()
			});
		}
		return this.counters.get(fn);
	}
}
console.log("====================================== Start ===========================");
 
const test = new CircuitBreaker();
 
function testFunc() {
	throw new Error();
}
 
while (true) {
	try {
		test.call(testFunc);
	} catch (e) {
	}
}
 
 
console.log("====================================== End ===========================");