-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathCircuit.js
132 lines (112 loc) · 4.04 KB
/
Circuit.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
'use strict';
const EventEmitter = require('events').EventEmitter;
const Promise = require('bluebird');
const promisifyIfFunction = require('./utils').promisifyIfFunction;
const TimeOutError = require('./TimeOutError');
const CircuitBrokenError = require('../lib/CircuitBrokenError');
const consts = require('./consts');
const defaultOptions = {
isFailure: () => true
};
/**
* Class that can sit on top of a Brakes. It's basically just a pair of primary and fallback Promises you can put on
* top of a Brake that monitors a common Service (eg: ).
*/
class Circuit extends EventEmitter {
constructor(brakes, main, fallback, options) {
super();
if (!(brakes instanceof EventEmitter)) {
throw new Error(consts.NO_BRAKES);
}
this._brakes = brakes;
if (!main || typeof main !== 'function') {
throw new Error(consts.NO_FUNCTION);
}
else if (fallback) {
if (typeof fallback !== 'function') {
if (options) {
throw new Error(consts.NO_FUNCTION);
}
options = fallback;
fallback = undefined;
}
}
this._opts = Object.assign({}, defaultOptions, options);
this._this = this._opts.this || this;
this._serviceCall = promisifyIfFunction(main, this._opts.isPromise, this._opts.isFunction);
if (fallback) {
this.fallback(fallback, this._opts.isPromise, this._opts.isFunction);
}
}
exec() {
this._brakes.emit('exec');
// Save circuit generation to scope so we can compare it
// to the current generation when a request fails.
// This prevents failures from bleeding between circuit generations.
const execGeneration = this._brakes._circuitGeneration;
if (this._brakes._circuitOpen) {
this._brakes._stats.shortCircuit();
if (this._fallback) {
return this._fallback.apply(this, arguments);
}
else if (this._brakes._fallback) {
return this._brakes._fallback.apply(this, arguments);
}
return Promise.reject(new CircuitBrokenError(this._brakes.name, this._brakes._stats._totals, this._brakes._opts.threshold));
}
const startTime = Date.now();
// we use _execPromise() wrapper on the service call promise
// to allow us to more easily hook in stats reporting
return this._execPromise
.apply(this, arguments)
.tap(() => this._brakes.emit('success', Date.now() - startTime))
.catch(err => {
const endTime = Date.now() - startTime;
// trigger hook listeners
if (err instanceof TimeOutError) {
this._brakes.emit('timeout', endTime, err, execGeneration);
}
else if (this._opts.isFailure(err)) {
this._brakes.emit('failure', endTime, err, execGeneration);
}
// if fallback exists, call it upon failure
// there are no listeners or stats collection for
// the fallback function. The function is fire-and-forget
// as far as `Brakes` is concerned
if (this._fallback) {
return this._fallback.apply(this, arguments);
}
else if (this._brakes._fallback) {
return this._brakes._fallback.apply(this, arguments);
}
if (err && err.message && this._brakes.name && this._brakes._opts.modifyError) {
err.message = `[Breaker: ${this._brakes.name}] ${err.message}`;
}
return Promise.reject(err);
});
}
/*
Execute main service call
*/
_execPromise() {
return new Promise((resolve, reject) => {
// start timeout timer
const timeoutTimer = setTimeout(() => {
reject(new TimeOutError(consts.TIMEOUT));
}, this._opts.timeout || this._brakes._opts.timeout);
this._serviceCall.apply(this._this, arguments).then(result => {
clearTimeout(timeoutTimer);
resolve(result);
}).catch(err => {
clearTimeout(timeoutTimer);
reject(err);
});
timeoutTimer.unref();
});
}
fallback(func, isPromise, isFunction) {
this._fallback = promisifyIfFunction(func, isPromise, isFunction);
return this._fallback;
}
}
module.exports = Circuit;