UNPKG

3.41 kBJavaScriptView Raw
1'use strict';
2
3function parseRetryOptions (options) {
4 options = options || { };
5
6 if (options.parsed) {
7 return options;
8 }
9
10 options = Object.clone(options);
11
12 if (typeof options === 'number') {
13 if (options < 1000) {
14 options = { count: options };
15 } else {
16 options = { timeout: options };
17 }
18 }
19 // don't use count by default
20 if (typeof options.count !== 'number') {
21 options.count = -1;
22 }
23 // 100ms default delay
24 if (typeof options.delay !== 'number') {
25 options.delay = 100;
26 }
27 // 30s default timeout
28 if (typeof options.timeout !== 'number') {
29 if (options.count > 0) {
30 options.timeout = 0;
31 } else {
32 options.timeout = 30000;
33 }
34 }
35
36 options.parsed = true;
37 return options;
38}
39
40// Promise extension for a delay
41Promise.delay = Promise.prototype.delay = function (timeout) {
42 return new Promise((resolve) => {
43 setTimeout(() => {
44 resolve(timeout);
45 }, timeout);
46 });
47};
48
49Promise.sleep = Promise.prototype.sleep = Promise.prototype.delay;
50
51// Validator promises
52const validation = {
53 ok (value) {
54 return Promise.resolve(value ? value : true);
55 },
56 fail (reason) {
57 return Promise.reject(reason ? reason : 'validation failed');
58 },
59};
60
61/**
62 * Promise retry mechanisms
63 */
64function timeoutPromise (promise, options, value, context) {
65 return new Promise((resolve, reject) => {
66 try {
67 promise.bind(context)(value).then((result) => {
68 resolve(result);
69 }, (error) => {
70 options.lastError = error;
71 setTimeout(() => {
72 reject(error);
73 }, options.delay);
74 });
75 } catch (error) {
76 options.lastError = error;
77 setTimeout(() => {
78 reject(error);
79 }, options.delay);
80 }
81 });
82}
83
84/**
85 * Retry a promise
86 */
87const retry = function (promise, options, value, context) {
88 options = parseRetryOptions(options);
89 options.start = options.start || Date.now();
90 if (typeof options.counter !== 'number') {
91 options.counter = -1;
92 }
93 let errorMessage;
94 if (options.timeout > 0 && Date.now() > options.start + options.timeout) {
95 errorMessage = `timeout of ${ options.timeout } ms exceeded (${ options.counter } attempts).`;
96 }
97 if (options.count > 0 && options.counter >= options.count - 1) {
98 errorMessage = `retry count of ${ options.count } exceeded.`;
99 }
100 if (errorMessage) {
101 const error = new Error(errorMessage);
102 if (options.lastError) {
103 error.stack = options.lastError.stack;
104 }
105 throw error;
106 }
107
108 options.counter++;
109
110 return timeoutPromise(promise, options, value, context).
111 then((result) => result, (error) => {
112 if (error && (error.name === 'ReferenceError' || error.name === 'SyntaxError' ||
113 error.name === 'TypeError' || error.name === 'RangeError' ||
114 error.name === 'EvalError' || error.name === 'InternalError' ||
115 error.name === 'URIError' || error.name === 'UnhandledUrlParameterError' ||
116 error.name === 'RetryError' || error.group && error.group === 'RetryError')) {
117 throw error;
118 } else {
119 return retry(promise, options, value, context);
120 }
121 });
122};
123
124// Promise extension for retry as a then-able
125Promise.prototype.thenRetry = function (promise, options) {
126 return this.then((value) => retry(promise, options, value));
127};
128
129module.exports = {
130 retry,
131 validation,
132};