← Усі задачіNode.js · Senior
Circuit breaker
Коли залежний сервіс лежить, кожен запит до нього чекає таймаут і забирає ресурси. Напишіть createBreaker(fn, { failureThreshold, resetMs, now }) з методами call(...args) і state().
- closed: виклики йдуть у fn; після failureThreshold помилок поспіль автомат переходить в open (успіх скидає лічильник).
- open: call одразу відхиляється з Circuit open, fn не викликається.
- Через resetMs після відкриття стан - half-open: пропускається один пробний виклик (інші паралельні отримують Circuit open). Успіх закриває автомат, помилка знову відкриває його на resetMs.
Ctrl+Enter - запустити тести. Tab - відступ; щоб вийти з редактора клавіатурою, натисніть Esc, а потім Tab. Код зберігається в цьому браузері.
Тести (6)
let t = 0; const b = createBreaker(async (x) => x * 2, { failureThreshold: 3, resetMs: 1000, now: () => t }); expect(await b.call(21)).toBe(42); expect(b.state()).toBe('closed');
let t = 0; const f = fn(async () => { throw new Error('timeout'); }); const b = createBreaker(f, { failureThreshold: 3, resetMs: 1000, now: () => t }); for (let i = 0; i < 3; i++) await expect(b.call()).rejects.toThrow('timeout'); expect(b.state()).toBe('open'); await expect(b.call()).rejects.toThrow('Circuit open'); expect(f).toHaveBeenCalledTimes(3);
let t = 0; let fail = true; const b = createBreaker(async () => { if (fail) throw new Error('down'); return 'ok'; }, { failureThreshold: 2, resetMs: 1000, now: () => t }); await expect(b.call()).rejects.toThrow('down'); await expect(b.call()).rejects.toThrow('down'); t = 999; expect(b.state()).toBe('open'); t = 1000; expect(b.state()).toBe('half-open'); fail = false; expect(await b.call()).toBe('ok'); expect(b.state()).toBe('closed');
let t = 0; const b = createBreaker(async () => { throw new Error('down'); }, { failureThreshold: 1, resetMs: 500, now: () => t }); await expect(b.call()).rejects.toThrow('down'); t = 500; await expect(b.call()).rejects.toThrow('down'); expect(b.state()).toBe('open'); t = 900; await expect(b.call()).rejects.toThrow('Circuit open');
let t = 0; let ok = false; const b = createBreaker(async () => { if (!ok) throw new Error('down'); await wait(30); return 'ok'; }, { failureThreshold: 1, resetMs: 100, now: () => t }); await expect(b.call()).rejects.toThrow('down'); t = 100; ok = true; const first = b.call(); await expect(b.call()).rejects.toThrow('Circuit open'); expect(await first).toBe('ok');
let t = 0; let n = 0; const b = createBreaker(async () => { n++; if (n % 2) throw new Error('flaky'); return n; }, { failureThreshold: 2, resetMs: 1000, now: () => t }); await expect(b.call()).rejects.toThrow('flaky'); expect(await b.call()).toBe(2); await expect(b.call()).rejects.toThrow('flaky'); expect(b.state()).toBe('closed');