circuit-state
Advanced tools
Comparing version
259
index.js
@@ -1,152 +0,237 @@ | ||
'use strict'; | ||
(function (global, factory) { | ||
if (typeof module === 'object' && typeof module.exports === 'object') { | ||
module.exports = factory(); | ||
} | ||
else if (typeof define === 'function' && define.amd) { | ||
define([], factory); | ||
} | ||
else { | ||
global.CircuitBreakerState = factory().CircuitBreakerState; | ||
global.CircuitBreakerOpenError = factory().CircuitBreakerOpenError; | ||
} | ||
}(typeof window !== 'undefined' ? window : this, function () { | ||
const OPEN = Symbol('open'); //eslint-disable-line no-undef | ||
const CLOSED = Symbol('closed'); //eslint-disable-line no-undef | ||
const HALF_OPEN = Symbol('half_open'); //eslint-disable-line no-undef | ||
const OPEN = Symbol('open'); | ||
const CLOSED = Symbol('closed'); | ||
const HALF_OPEN = Symbol('half_open'); | ||
class CircuitBreakerOpenError extends Error { | ||
const SUCCEEDED_EVENT = 'succeeded'; | ||
const FAILED_EVENT = 'failed'; | ||
const CLOSED_EVENT = 'closed'; | ||
const OPEN_EVENT = 'opened'; | ||
const HALF_OPEN_EVENT = 'half_opened'; | ||
const EXECUTIONS = 'executions'; | ||
const SUCCESSES = 'successes'; | ||
const FAILURES = 'failures'; | ||
class CircuitBreakerOpenError extends Error { | ||
constructor() { | ||
super('Circuit breaker is open'); | ||
this.name = 'CircuitBreakerOpenError'; | ||
this.code = 'EPERM'; | ||
super('Circuit breaker is open'); | ||
this.name = 'CircuitBreakerOpenError'; | ||
this.code = 'EPERM'; | ||
} | ||
} | ||
} | ||
class Stats { | ||
class CircuitBreakerEventEmitter { | ||
constructor() { | ||
this._events = {}; | ||
} | ||
on(event, listener) { | ||
if (!this._events[event]) { | ||
this._events[event] = []; | ||
} | ||
this._events[event].push(listener); | ||
return this; | ||
} | ||
once(event, listener) { | ||
const onceWrapper = (...args) => { | ||
listener(...args); | ||
this.removeListener(event, onceWrapper); | ||
}; | ||
this.on(event, onceWrapper); | ||
return this; | ||
} | ||
removeListener(event, listener) { | ||
if (!this._events[event]) return this; | ||
this._events[event] = this._events[event].filter((l) => l !== listener); | ||
return this; | ||
} | ||
off(event, listener) { | ||
return this.removeListener(event, listener); | ||
} | ||
emit(event, ...args) { | ||
if (!this._events[event]) return false; | ||
this._events[event].forEach((listener) => listener(...args)); | ||
return true; | ||
} | ||
removeAllListeners(event) { | ||
if (event) { | ||
delete this._events[event]; | ||
} else { | ||
this._events = {}; | ||
} | ||
return this; | ||
} | ||
} | ||
class Stats { | ||
constructor(cbState) { | ||
this._cbState = cbState; | ||
this._counts = { | ||
executions: 0, | ||
successes: 0, | ||
failures: 0 | ||
}; | ||
this._cbState = cbState; | ||
this._counts = { | ||
executions: 0, | ||
successes: 0, | ||
failures: 0 | ||
}; | ||
} | ||
increment(key) { | ||
if (key === 'open') { | ||
return; | ||
} | ||
if (!this._counts[key]) { | ||
this._counts[key] = 0; | ||
} | ||
if (this._counts[key] === Number.MAX_SAFE_INTEGER) { | ||
this._counts[key] = 0; | ||
} | ||
this._counts[key] += 1; | ||
if (key === 'open') { | ||
return; | ||
} | ||
if (!this._counts[key]) { | ||
this._counts[key] = 0; | ||
} | ||
if (this._counts[key] === Number.MAX_SAFE_INTEGER) { | ||
this._counts[key] = 0; | ||
} | ||
this._counts[key] += 1; | ||
} | ||
reset(key) { | ||
this._counts[key] = 0; | ||
this._counts[key] = 0; | ||
} | ||
resetAll() { | ||
Object.keys(this._counts).forEach((key) => { | ||
this.reset(key); | ||
}); | ||
Object.keys(this._counts).forEach((key) => { | ||
this.reset(key); | ||
}); | ||
} | ||
snapshot() { | ||
return Object.assign({ open : this._cbState.open, ...this._counts }); | ||
return Object.assign({ open: this._cbState.open, ...this._counts }); | ||
} | ||
} | ||
} | ||
class CircuitBreakerState { | ||
constructor({ maxFailures = 3, resetTime = 10000, resetManually = false } = {}) { | ||
this._state = CLOSED; | ||
this._maxFailures = maxFailures; | ||
this._failures = 0; | ||
this._resetTimer = undefined; | ||
this._resetTime = resetTime; | ||
this._resetManually = resetManually; | ||
this._stats = new Stats(this); | ||
class CircuitBreakerState { | ||
constructor({ maxFailures = 3, resetTime = 10000 } = {}) { | ||
this._state = CLOSED; | ||
this._maxFailures = maxFailures; | ||
this._failures = 0; | ||
this._resetTimer = undefined; | ||
this._resetTime = resetTime; | ||
this._resetManually = resetTime <= 0 ? true : false; | ||
this._stats = new Stats(this); | ||
this._events = new CircuitBreakerEventEmitter(); | ||
} | ||
static create(options) { | ||
return new CircuitBreakerState(options); | ||
return new CircuitBreakerState(options); | ||
} | ||
get maxFailures() { | ||
return this._maxFailures; | ||
return this._maxFailures; | ||
} | ||
get resetTime() { | ||
return this._resetTime; | ||
return this._resetTime; | ||
} | ||
get stats() { | ||
return this._stats; | ||
return this._stats; | ||
} | ||
get events() { | ||
return this._events; | ||
} | ||
_open() { | ||
clearTimeout(this._resetTimer); | ||
this._state = OPEN; | ||
if (!this._resetManually) { | ||
this._resetTimer = setTimeout(() => { | ||
this._halfOpen(); | ||
}, this._resetTime); | ||
this._resetTimer.unref(); | ||
clearTimeout(this._resetTimer); | ||
this._state = OPEN; | ||
this._events.emit(OPEN_EVENT, this._stats.snapshot()); | ||
if (!this._resetManually) { | ||
this._resetTimer = setTimeout(() => { | ||
this._halfOpen(); | ||
}, this._resetTime); | ||
if (this._resetTimer.unref) { | ||
this._resetTimer.unref(); | ||
} | ||
this._failures = 0; | ||
} | ||
this._failures = 0; | ||
} | ||
_halfOpen() { | ||
this._state = HALF_OPEN; | ||
this._state = HALF_OPEN; | ||
this._events.emit(HALF_OPEN_EVENT, this._stats.snapshot()); | ||
} | ||
_close() { | ||
clearTimeout(this._resetTimer); | ||
this._state = CLOSED; | ||
clearTimeout(this._resetTimer); | ||
this._state = CLOSED; | ||
this._events.emit(CLOSED_EVENT, this._stats.snapshot()); | ||
} | ||
fail() { | ||
++this._failures; | ||
if (this.halfOpen) { | ||
this._open(); | ||
return; | ||
} | ||
if (this._failures === this._maxFailures) { | ||
this._open(); | ||
} | ||
this._stats.increment('executions'); | ||
this._stats.increment('failures'); | ||
++this._failures; | ||
if (this.halfOpen) { | ||
this._open(); | ||
return; | ||
} | ||
if (this._failures === this._maxFailures) { | ||
this._open(); | ||
} | ||
this._stats.increment(EXECUTIONS); | ||
this._stats.increment(FAILURES); | ||
this._events.emit(FAILED_EVENT, this._stats.snapshot()); | ||
} | ||
succeed() { | ||
this._failures = 0; | ||
if (this.halfOpen) { | ||
this._close(); | ||
} | ||
this._stats.increment('executions'); | ||
if (this.closed) { | ||
this._stats.increment('successes'); | ||
} | ||
else { | ||
this._stats.increment('failures'); | ||
} | ||
this._failures = 0; | ||
if (this.halfOpen) { | ||
this._close(); | ||
} | ||
this._stats.increment(EXECUTIONS); | ||
if (this.closed) { | ||
this._stats.increment(SUCCESSES); | ||
} | ||
else { | ||
this._stats.increment(FAILURES); | ||
} | ||
this._events.emit(this.closed ? SUCCEEDED_EVENT : FAILED_EVENT, this._stats.snapshot()); | ||
} | ||
tryReset() { | ||
clearTimeout(this._resetTimer); | ||
this._halfOpen(); | ||
clearTimeout(this._resetTimer); | ||
this._halfOpen(); | ||
} | ||
get open() { | ||
return this._state === OPEN; | ||
return this._state === OPEN; | ||
} | ||
get halfOpen() { | ||
return this._state === HALF_OPEN; | ||
return this._state === HALF_OPEN; | ||
} | ||
get closed() { | ||
return this._state == CLOSED; | ||
return this._state === CLOSED; | ||
} | ||
test() { | ||
if (this.open) { | ||
return new CircuitBreakerOpenError(); | ||
} | ||
if (this.open) { | ||
return new CircuitBreakerOpenError(); | ||
} | ||
} | ||
} | ||
} | ||
module.exports = CircuitBreakerState; | ||
return CircuitBreakerState; | ||
})); |
{ | ||
"name": "circuit-state", | ||
"version": "1.0.0", | ||
"version": "3.0.0", | ||
"description": "Circuit breaker state machine.", | ||
@@ -24,6 +24,6 @@ "author": "Trevor Livingston", | ||
"devDependencies": { | ||
"eslint": "^4.18.1", | ||
"nyc": "^11.4.1", | ||
"tape": "^4.9.0" | ||
"eslint": "^4.19.1", | ||
"nyc": "^11.9.0", | ||
"tape": "^4.17.0" | ||
} | ||
} |
@@ -21,4 +21,3 @@ | ||
- `maxFailures` - Maximum number of failures before circuit breaker flips open. Default `3`. | ||
- `resetTime` - Time in ms before an open circuit breaker returns to a half-open state. Default `10000`. | ||
- `resetManually` - Boolean value representing whether or not to attempt reset manually vs on timer. Default `false`. | ||
- `resetTime` - Time in ms before an open circuit breaker returns to a half-open state. Default `10000`. If 0 or less, manual resets will be used. | ||
- `CircuitBreakerState.create(options)` - Creates a new `CircuitBreakerState` instance. | ||
@@ -30,3 +29,3 @@ | ||
- `fail()` - Record a failure. This may trip open the circuit breaker. | ||
- `test()` - Tests for the state being open. If so, returns an error (may be returned to user). | ||
- `test()` - Utility function to test for the state being open. If so, returns an error (may be returned to user). | ||
- `tryReset()` - Flips to half-open and cancels reset timer (if any). | ||
@@ -39,2 +38,3 @@ - `open` - Is `true` if this circuit breaker is open. Read-only. | ||
- `resetTime` - Read-only. | ||
- `events` - read only event emitter | ||
@@ -48,3 +48,14 @@ Stats object: | ||
Event emitter: | ||
The `events` property on the `CircuitBreakerState` is an event emitter to which you can listen to the following events: | ||
- `opened` | ||
- `closed` | ||
- `half_opened` | ||
- `succeeded` | ||
- `failed` | ||
All of these events will receive a `snapshot` of the `Stats` object. | ||
### Example usage | ||
@@ -69,3 +80,5 @@ | ||
if (error) { | ||
callback(error); | ||
setImmediate(() => { | ||
callback(error); | ||
}); | ||
return; | ||
@@ -96,4 +109,4 @@ } | ||
class Circuit { | ||
constructor(promise) { | ||
this._promise = promise; | ||
constructor(asyncFunc) { | ||
this._asyncFunc = asyncFunc; | ||
this._cb = new CircuitBreakerState(); | ||
@@ -104,2 +117,3 @@ } | ||
// Fail fast | ||
if (error) { | ||
@@ -110,3 +124,3 @@ throw error; | ||
try { | ||
const result = await this._promise(...args); | ||
const result = await this._asyncFunc(...args); | ||
this._cb.succeed(); | ||
@@ -113,0 +127,0 @@ return result; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
128
12.28%10845
-45.61%4
-60%201
-59.39%1
Infinity%