-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
84 lines (80 loc) · 3.17 KB
/
index.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
const getPromise = (fn, args) => {
return new Promise((resolve, reject) => {
if (!args) args = [];
args.push((err, data) => {
if (err) return reject(err);
return resolve(data);
});
fn.apply(null, args);
});
};
const clone = (obj) => {
if (obj === null || typeof obj !== "object") {
return obj;
} else if (Array.isArray(obj)) {
let clonedArr = [];
for (const data of obj) {
clonedArr.push(clone(data));
}
return clonedArr;
} else {
let clonedObj = {};
const keys = Object.keys(obj);
for (const key of keys) {
clonedObj[key] = clone(obj[key]);
}
return clonedObj;
}
}
const onAttemptFailFallback = async (data) => {
const interval = data.exponential ? Math.min(Math.pow(data.factor, data.currentRetry) * data.interval, data.maxBackoff) : data.interval;
// if interval is set to zero, do not use setTimeout, gain 1 event loop tick
if (interval) await new Promise(r => setTimeout(r, interval + data.jitter));
}
/**
* Retry system with async / await
*
* @param {Function} fn : function to execute
* @param {Array} args : arguments of fn function
* @param {Object} config : arguments of fn function
* @property {Number} config.retriesMax : number of retries, by default 3
* @property {Number} config.interval : interval (in ms) between retry, by default 0
* @property {Boolean} config.exponential : use exponential retry interval, by default true
* @property {Number} config.factor: interval incrementation factor
* @property {Number} config.isCb: is fn a callback style function ?
* @property {Function} config.onAttemptFail: use a callback when an error has occured
* @property {Number} config.maxBackoff : maximum time of backoff, by default 30 seconds
*/
module.exports = async (fn, args = [], config = {}) => {
const retriesMax = config.retriesMax || 3;
let interval = config.interval || 0;
const jitter = config.jitter ? Math.floor(Math.random() * config.jitter) + 1 : 0;
const exponential = Object.prototype.hasOwnProperty.call(config, 'exponential') ? config.exponential : true;
const factor = config.factor || 2;
const onAttemptFail = typeof config.onAttemptFail === 'function' ? config.onAttemptFail : onAttemptFailFallback;
const maxBackoff = config.maxBackoff || 30000;
for (let i = 0; i < retriesMax; i++) {
try {
if (!config.isCb) {
const val = await fn.apply(null, args);
return val;
} else {
const val = await getPromise(fn, clone(args));
return val;
}
} catch (error) {
if(retriesMax === i+1 || (Object.prototype.hasOwnProperty.call(error, 'retryable') && !error.retryable)) throw error;
const result = await onAttemptFail({
error,
currentRetry: i,
retriesMax,
interval,
exponential,
factor,
jitter,
maxBackoff
});
if (!result && typeof config.onAttemptFail === 'function') return
}
}
};