1 | 'use strict';
|
2 |
|
3 | function 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 |
|
20 | if (typeof options.count !== 'number') {
|
21 | options.count = -1;
|
22 | }
|
23 |
|
24 | if (typeof options.delay !== 'number') {
|
25 | options.delay = 100;
|
26 | }
|
27 |
|
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 |
|
41 | Promise.delay = Promise.prototype.delay = function (timeout) {
|
42 | return new Promise((resolve) => {
|
43 | setTimeout(() => {
|
44 | resolve(timeout);
|
45 | }, timeout);
|
46 | });
|
47 | };
|
48 |
|
49 | Promise.sleep = Promise.prototype.sleep = Promise.prototype.delay;
|
50 |
|
51 |
|
52 | const 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 |
|
63 |
|
64 | function 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 |
|
86 |
|
87 | const 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) => {
|
112 | return result;
|
113 | }, (error) => {
|
114 | if (error && (error.name === 'ReferenceError' || error.name === 'SyntaxError' ||
|
115 | error.name === 'TypeError' || error.name === 'RangeError' ||
|
116 | error.name === 'EvalError' || error.name === 'InternalError' ||
|
117 | error.name === 'URIError' || error.name === 'UnhandledUrlParameterError' ||
|
118 | error.name === 'RetryError' || error.group && error.group === 'RetryError')) {
|
119 | throw error;
|
120 | } else {
|
121 | return retry(promise, options, value, context);
|
122 | }
|
123 | });
|
124 | };
|
125 |
|
126 |
|
127 | Promise.prototype.thenRetry = function (promise, options) {
|
128 | return this.then((value) => {
|
129 | return retry(promise, options, value);
|
130 | });
|
131 | };
|
132 |
|
133 | module.exports = {
|
134 | retry,
|
135 | validation
|
136 | };
|