@naturalcycles/js-lib 14.80.1 → 14.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decorators/retry.decorator.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/promise/pRetry.d.ts +17 -2
- package/dist/promise/pRetry.js +72 -40
- package/dist-esm/decorators/retry.decorator.js +2 -2
- package/dist-esm/index.js +2 -2
- package/dist-esm/promise/pRetry.js +70 -39
- package/package.json +1 -1
- package/src/decorators/retry.decorator.ts +2 -2
- package/src/index.ts +2 -1
- package/src/promise/pRetry.ts +105 -41
|
@@ -6,7 +6,7 @@ const __1 = require("..");
|
|
|
6
6
|
function _Retry(opt = {}) {
|
|
7
7
|
return (target, key, descriptor) => {
|
|
8
8
|
const originalFn = descriptor.value;
|
|
9
|
-
descriptor.value = (0, __1.
|
|
9
|
+
descriptor.value = (0, __1.pRetryFn)(originalFn, opt);
|
|
10
10
|
return descriptor;
|
|
11
11
|
};
|
|
12
12
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export * from './promise/pFilter';
|
|
|
41
41
|
export * from './promise/pHang';
|
|
42
42
|
import { pMap, PMapOptions } from './promise/pMap';
|
|
43
43
|
export * from './promise/pProps';
|
|
44
|
-
import { pRetry, PRetryOptions } from './promise/pRetry';
|
|
44
|
+
import { pRetry, pRetryFn, PRetryOptions } from './promise/pRetry';
|
|
45
45
|
export * from './promise/pState';
|
|
46
46
|
import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout';
|
|
47
47
|
export * from './promise/pTuple';
|
|
@@ -60,4 +60,4 @@ import { PQueue, PQueueCfg } from './promise/pQueue';
|
|
|
60
60
|
export * from './seq/seq';
|
|
61
61
|
export * from './math/stack.util';
|
|
62
62
|
export type { AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, PromiseDecoratorCfg, PromiseDecoratorResp, ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Admin401ErrorData, Admin403ErrorData, StringMap, PromiseMap, AnyObject, AnyFunction, ValuesOf, ValueOf, KeyValueTuple, ObjectMapper, ObjectPredicate, InstanceId, IsoDate, IsoDateTime, Reviver, PMapOptions, Mapper, AsyncMapper, Predicate, AsyncPredicate, BatchResult, DeferredPromise, PRetryOptions, PTimeoutOptions, TryCatchOptions, StringifyAnyOptions, JsonStringifyFunction, Merge, ReadonlyDeep, Promisable, Simplify, ConditionalPick, ConditionalExcept, Class, UnixTimestamp, BaseDBEntity, SavedDBEntity, Saved, Unsaved, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, JsonSchema, JsonSchemaAny, JsonSchemaOneOf, JsonSchemaAllOf, JsonSchemaAnyOf, JsonSchemaNot, JsonSchemaRef, JsonSchemaConst, JsonSchemaEnum, JsonSchemaString, JsonSchemaNumber, JsonSchemaBoolean, JsonSchemaNull, JsonSchemaRootObject, JsonSchemaObject, JsonSchemaArray, JsonSchemaTuple, JsonSchemaBuilder, CommonLogLevel, CommonLogWithLevelFunction, CommonLogFunction, CommonLogger, };
|
|
63
|
-
export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
|
|
63
|
+
export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SKIP = exports.END = exports.PQueue = exports.commonLoggerCreate = exports.commonLoggerPrefix = exports.commonLoggerPipe = exports.commonLogLevelNumber = exports.commonLoggerNoop = exports.commonLoggerMinLevel = exports.JsonSchemaAnyBuilder = exports.jsonSchema = exports._stringifyAny = exports._TryCatch = exports._tryCatch = exports.pTimeoutFn = exports.pTimeout = exports.pRetry = exports.AggregatedError = exports.pDefer = exports.ErrorMode = exports._noop = exports._passNothingPredicate = exports._passthroughPredicate = exports._passUndefinedMapper = exports._passthroughMapper = exports.pMap = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._createPromiseDecorator = exports.is = void 0;
|
|
3
|
+
exports.SKIP = exports.END = exports.PQueue = exports.commonLoggerCreate = exports.commonLoggerPrefix = exports.commonLoggerPipe = exports.commonLogLevelNumber = exports.commonLoggerNoop = exports.commonLoggerMinLevel = exports.JsonSchemaAnyBuilder = exports.jsonSchema = exports._stringifyAny = exports._TryCatch = exports._tryCatch = exports.pTimeoutFn = exports.pTimeout = exports.pRetryFn = exports.pRetry = exports.AggregatedError = exports.pDefer = exports.ErrorMode = exports._noop = exports._passNothingPredicate = exports._passthroughPredicate = exports._passUndefinedMapper = exports._passthroughMapper = exports.pMap = exports._objectKeys = exports._stringMapEntries = exports._stringMapValues = exports._createPromiseDecorator = exports.is = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
(0, tslib_1.__exportStar)(require("./array/array.util"), exports);
|
|
6
6
|
(0, tslib_1.__exportStar)(require("./lazy"), exports);
|
|
@@ -53,6 +53,7 @@ Object.defineProperty(exports, "pMap", { enumerable: true, get: function () { re
|
|
|
53
53
|
(0, tslib_1.__exportStar)(require("./promise/pProps"), exports);
|
|
54
54
|
const pRetry_1 = require("./promise/pRetry");
|
|
55
55
|
Object.defineProperty(exports, "pRetry", { enumerable: true, get: function () { return pRetry_1.pRetry; } });
|
|
56
|
+
Object.defineProperty(exports, "pRetryFn", { enumerable: true, get: function () { return pRetry_1.pRetryFn; } });
|
|
56
57
|
(0, tslib_1.__exportStar)(require("./promise/pState"), exports);
|
|
57
58
|
const pTimeout_1 = require("./promise/pTimeout");
|
|
58
59
|
Object.defineProperty(exports, "pTimeout", { enumerable: true, get: function () { return pTimeout_1.pTimeout; } });
|
package/dist/promise/pRetry.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface PRetryOptions {
|
|
|
5
5
|
* Can be used to identify the place in the code that failed.
|
|
6
6
|
*/
|
|
7
7
|
name?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Timeout for each Try, in milliseconds.
|
|
10
|
+
*
|
|
11
|
+
* Defaults to 60_000
|
|
12
|
+
*/
|
|
13
|
+
timeout?: number;
|
|
8
14
|
/**
|
|
9
15
|
* How many attempts to try.
|
|
10
16
|
* First attempt is not a retry, but "initial try". It still counts.
|
|
@@ -29,7 +35,7 @@ export interface PRetryOptions {
|
|
|
29
35
|
*
|
|
30
36
|
* @default () => true
|
|
31
37
|
*/
|
|
32
|
-
predicate?: (err:
|
|
38
|
+
predicate?: (err: Error, attempt: number, maxAttempts: number) => boolean;
|
|
33
39
|
/**
|
|
34
40
|
* Log the first attempt (which is not a "retry" yet).
|
|
35
41
|
*
|
|
@@ -62,9 +68,18 @@ export interface PRetryOptions {
|
|
|
62
68
|
* Default to `console`
|
|
63
69
|
*/
|
|
64
70
|
logger?: CommonLogger;
|
|
71
|
+
/**
|
|
72
|
+
* Defaults to true.
|
|
73
|
+
* If true - preserves the stack trace in case of a Timeout (usually - very useful!).
|
|
74
|
+
* It has a certain perf cost.
|
|
75
|
+
*
|
|
76
|
+
* @experimental
|
|
77
|
+
*/
|
|
78
|
+
keepStackTrace?: boolean;
|
|
65
79
|
}
|
|
66
80
|
/**
|
|
67
81
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
68
82
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
69
83
|
*/
|
|
70
|
-
export declare function
|
|
84
|
+
export declare function pRetryFn<T extends AnyFunction>(fn: T, opt?: PRetryOptions): T;
|
|
85
|
+
export declare function pRetry<T>(fn: (attempt: number) => Promise<T>, opt?: PRetryOptions): Promise<T>;
|
package/dist/promise/pRetry.js
CHANGED
|
@@ -1,58 +1,90 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pRetry = void 0;
|
|
3
|
+
exports.pRetry = exports.pRetryFn = void 0;
|
|
4
4
|
const __1 = require("..");
|
|
5
|
+
const pTimeout_1 = require("./pTimeout");
|
|
5
6
|
/**
|
|
6
7
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
7
8
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
10
|
+
function pRetryFn(fn, opt = {}) {
|
|
11
|
+
return async function pRetryFunction(...args) {
|
|
12
|
+
return await pRetry(() => fn.call(this, ...args), opt);
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
exports.pRetryFn = pRetryFn;
|
|
16
|
+
async function pRetry(fn, opt = {}) {
|
|
17
|
+
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, keepStackTrace = true, timeout, } = opt;
|
|
18
|
+
const fakeError = keepStackTrace ? new Error('RetryError') : undefined;
|
|
12
19
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
|
|
13
20
|
if (opt.logAll) {
|
|
14
|
-
logFirstAttempt = logRetries = logFailures = true;
|
|
21
|
+
logSuccess = logFirstAttempt = logRetries = logFailures = true;
|
|
15
22
|
}
|
|
16
23
|
if (opt.logNone) {
|
|
17
24
|
logSuccess = logFirstAttempt = logRetries = logFailures = false;
|
|
18
25
|
}
|
|
19
|
-
const fname =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const fname = name || fn.name || 'pRetry function';
|
|
27
|
+
let delay = initialDelay;
|
|
28
|
+
let attempt = 0;
|
|
29
|
+
let timer;
|
|
30
|
+
let timedOut = false;
|
|
31
|
+
return await new Promise((resolve, reject) => {
|
|
32
|
+
const rejectWithTimeout = () => {
|
|
33
|
+
timedOut = true; // to prevent more tries
|
|
34
|
+
const err = new pTimeout_1.TimeoutError(`"${fname}" timed out after ${timeout} ms`);
|
|
35
|
+
if (fakeError) {
|
|
36
|
+
// keep original stack
|
|
37
|
+
err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
|
|
38
|
+
}
|
|
39
|
+
reject(err);
|
|
40
|
+
};
|
|
41
|
+
const next = async () => {
|
|
42
|
+
if (timedOut)
|
|
43
|
+
return;
|
|
44
|
+
if (timeout) {
|
|
45
|
+
timer = setTimeout(rejectWithTimeout, timeout);
|
|
46
|
+
}
|
|
47
|
+
const started = Date.now();
|
|
48
|
+
try {
|
|
49
|
+
attempt++;
|
|
50
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
51
|
+
logger.log(`${fname} attempt #${attempt}...`);
|
|
36
52
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
const r = await fn(attempt);
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
if (logSuccess) {
|
|
56
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
|
|
57
|
+
}
|
|
58
|
+
resolve(r);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
if (logFailures) {
|
|
63
|
+
logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, (0, __1._stringifyAny)(err, {
|
|
64
|
+
includeErrorData: true,
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
if (attempt >= maxAttempts ||
|
|
68
|
+
(predicate && !predicate(err, attempt, maxAttempts))) {
|
|
69
|
+
// Give up
|
|
70
|
+
if (fakeError) {
|
|
71
|
+
// Preserve the original call stack
|
|
72
|
+
Object.defineProperty(err, 'stack', {
|
|
73
|
+
value: err.stack +
|
|
74
|
+
'\n --' +
|
|
75
|
+
fakeError.stack.replace('Error: RetryError', ''),
|
|
76
|
+
});
|
|
51
77
|
}
|
|
78
|
+
reject(err);
|
|
52
79
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
else {
|
|
81
|
+
// Retry after delay
|
|
82
|
+
delay *= delayMultiplier;
|
|
83
|
+
setTimeout(next, delay);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
void next();
|
|
88
|
+
});
|
|
57
89
|
}
|
|
58
90
|
exports.pRetry = pRetry;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pRetryFn } from '..';
|
|
2
2
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3
3
|
export function _Retry(opt = {}) {
|
|
4
4
|
return (target, key, descriptor) => {
|
|
5
5
|
const originalFn = descriptor.value;
|
|
6
|
-
descriptor.value =
|
|
6
|
+
descriptor.value = pRetryFn(originalFn, opt);
|
|
7
7
|
return descriptor;
|
|
8
8
|
};
|
|
9
9
|
}
|
package/dist-esm/index.js
CHANGED
|
@@ -38,7 +38,7 @@ export * from './promise/pFilter';
|
|
|
38
38
|
export * from './promise/pHang';
|
|
39
39
|
import { pMap } from './promise/pMap';
|
|
40
40
|
export * from './promise/pProps';
|
|
41
|
-
import { pRetry } from './promise/pRetry';
|
|
41
|
+
import { pRetry, pRetryFn } from './promise/pRetry';
|
|
42
42
|
export * from './promise/pState';
|
|
43
43
|
import { pTimeout, pTimeoutFn } from './promise/pTimeout';
|
|
44
44
|
export * from './promise/pTuple';
|
|
@@ -55,4 +55,4 @@ export * from './string/safeJsonStringify';
|
|
|
55
55
|
import { PQueue } from './promise/pQueue';
|
|
56
56
|
export * from './seq/seq';
|
|
57
57
|
export * from './math/stack.util';
|
|
58
|
-
export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
|
|
58
|
+
export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
|
|
@@ -1,54 +1,85 @@
|
|
|
1
1
|
import { _since, _stringifyAny } from '..';
|
|
2
|
+
import { TimeoutError } from './pTimeout';
|
|
2
3
|
/**
|
|
3
4
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
4
5
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export function pRetryFn(fn, opt = {}) {
|
|
8
|
+
return async function pRetryFunction(...args) {
|
|
9
|
+
return await pRetry(() => fn.call(this, ...args), opt);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export async function pRetry(fn, opt = {}) {
|
|
13
|
+
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, keepStackTrace = true, timeout, } = opt;
|
|
14
|
+
const fakeError = keepStackTrace ? new Error('RetryError') : undefined;
|
|
9
15
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
|
|
10
16
|
if (opt.logAll) {
|
|
11
|
-
logFirstAttempt = logRetries = logFailures = true;
|
|
17
|
+
logSuccess = logFirstAttempt = logRetries = logFailures = true;
|
|
12
18
|
}
|
|
13
19
|
if (opt.logNone) {
|
|
14
20
|
logSuccess = logFirstAttempt = logRetries = logFailures = false;
|
|
15
21
|
}
|
|
16
|
-
const fname =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
const fname = name || fn.name || 'pRetry function';
|
|
23
|
+
let delay = initialDelay;
|
|
24
|
+
let attempt = 0;
|
|
25
|
+
let timer;
|
|
26
|
+
let timedOut = false;
|
|
27
|
+
return await new Promise((resolve, reject) => {
|
|
28
|
+
const rejectWithTimeout = () => {
|
|
29
|
+
timedOut = true; // to prevent more tries
|
|
30
|
+
const err = new TimeoutError(`"${fname}" timed out after ${timeout} ms`);
|
|
31
|
+
if (fakeError) {
|
|
32
|
+
// keep original stack
|
|
33
|
+
err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
|
|
34
|
+
}
|
|
35
|
+
reject(err);
|
|
36
|
+
};
|
|
37
|
+
const next = async () => {
|
|
38
|
+
if (timedOut)
|
|
39
|
+
return;
|
|
40
|
+
if (timeout) {
|
|
41
|
+
timer = setTimeout(rejectWithTimeout, timeout);
|
|
42
|
+
}
|
|
43
|
+
const started = Date.now();
|
|
44
|
+
try {
|
|
45
|
+
attempt++;
|
|
46
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
47
|
+
logger.log(`${fname} attempt #${attempt}...`);
|
|
33
48
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const r = await fn(attempt);
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
if (logSuccess) {
|
|
52
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`);
|
|
53
|
+
}
|
|
54
|
+
resolve(r);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
if (logFailures) {
|
|
59
|
+
logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, _stringifyAny(err, {
|
|
60
|
+
includeErrorData: true,
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
if (attempt >= maxAttempts ||
|
|
64
|
+
(predicate && !predicate(err, attempt, maxAttempts))) {
|
|
65
|
+
// Give up
|
|
66
|
+
if (fakeError) {
|
|
67
|
+
// Preserve the original call stack
|
|
68
|
+
Object.defineProperty(err, 'stack', {
|
|
69
|
+
value: err.stack +
|
|
70
|
+
'\n --' +
|
|
71
|
+
fakeError.stack.replace('Error: RetryError', ''),
|
|
72
|
+
});
|
|
48
73
|
}
|
|
74
|
+
reject(err);
|
|
49
75
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
76
|
+
else {
|
|
77
|
+
// Retry after delay
|
|
78
|
+
delay *= delayMultiplier;
|
|
79
|
+
setTimeout(next, delay);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
void next();
|
|
84
|
+
});
|
|
54
85
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pRetryFn, PRetryOptions } from '..'
|
|
2
2
|
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
4
4
|
export function _Retry(opt: PRetryOptions = {}): MethodDecorator {
|
|
5
5
|
return (target, key, descriptor) => {
|
|
6
6
|
const originalFn = descriptor.value
|
|
7
|
-
descriptor.value =
|
|
7
|
+
descriptor.value = pRetryFn(originalFn as any, opt)
|
|
8
8
|
return descriptor
|
|
9
9
|
}
|
|
10
10
|
}
|
package/src/index.ts
CHANGED
|
@@ -74,7 +74,7 @@ export * from './promise/pFilter'
|
|
|
74
74
|
export * from './promise/pHang'
|
|
75
75
|
import { pMap, PMapOptions } from './promise/pMap'
|
|
76
76
|
export * from './promise/pProps'
|
|
77
|
-
import { pRetry, PRetryOptions } from './promise/pRetry'
|
|
77
|
+
import { pRetry, pRetryFn, PRetryOptions } from './promise/pRetry'
|
|
78
78
|
export * from './promise/pState'
|
|
79
79
|
import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout'
|
|
80
80
|
export * from './promise/pTuple'
|
|
@@ -250,6 +250,7 @@ export {
|
|
|
250
250
|
pDefer,
|
|
251
251
|
AggregatedError,
|
|
252
252
|
pRetry,
|
|
253
|
+
pRetryFn,
|
|
253
254
|
pTimeout,
|
|
254
255
|
pTimeoutFn,
|
|
255
256
|
_tryCatch,
|
package/src/promise/pRetry.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _since, _stringifyAny, AnyFunction, CommonLogger } from '..'
|
|
2
|
+
import { TimeoutError } from './pTimeout'
|
|
2
3
|
|
|
3
4
|
export interface PRetryOptions {
|
|
4
5
|
/**
|
|
@@ -7,6 +8,13 @@ export interface PRetryOptions {
|
|
|
7
8
|
*/
|
|
8
9
|
name?: string
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Timeout for each Try, in milliseconds.
|
|
13
|
+
*
|
|
14
|
+
* Defaults to 60_000
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number
|
|
17
|
+
|
|
10
18
|
/**
|
|
11
19
|
* How many attempts to try.
|
|
12
20
|
* First attempt is not a retry, but "initial try". It still counts.
|
|
@@ -34,7 +42,7 @@ export interface PRetryOptions {
|
|
|
34
42
|
*
|
|
35
43
|
* @default () => true
|
|
36
44
|
*/
|
|
37
|
-
predicate?: (err:
|
|
45
|
+
predicate?: (err: Error, attempt: number, maxAttempts: number) => boolean
|
|
38
46
|
|
|
39
47
|
/**
|
|
40
48
|
* Log the first attempt (which is not a "retry" yet).
|
|
@@ -74,76 +82,132 @@ export interface PRetryOptions {
|
|
|
74
82
|
* Default to `console`
|
|
75
83
|
*/
|
|
76
84
|
logger?: CommonLogger
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Defaults to true.
|
|
88
|
+
* If true - preserves the stack trace in case of a Timeout (usually - very useful!).
|
|
89
|
+
* It has a certain perf cost.
|
|
90
|
+
*
|
|
91
|
+
* @experimental
|
|
92
|
+
*/
|
|
93
|
+
keepStackTrace?: boolean
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
/**
|
|
80
97
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
81
98
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
82
99
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
export function pRetryFn<T extends AnyFunction>(fn: T, opt: PRetryOptions = {}): T {
|
|
101
|
+
return async function pRetryFunction(this: any, ...args: any[]) {
|
|
102
|
+
return await pRetry(() => fn.call(this, ...args), opt)
|
|
103
|
+
} as any
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function pRetry<T>(
|
|
107
|
+
fn: (attempt: number) => Promise<T>,
|
|
108
|
+
opt: PRetryOptions = {},
|
|
109
|
+
): Promise<T> {
|
|
85
110
|
const {
|
|
86
111
|
maxAttempts = 4,
|
|
87
112
|
delay: initialDelay = 1000,
|
|
88
113
|
delayMultiplier = 2,
|
|
89
114
|
predicate,
|
|
90
115
|
logger = console,
|
|
91
|
-
name
|
|
116
|
+
name,
|
|
117
|
+
keepStackTrace = true,
|
|
118
|
+
timeout,
|
|
92
119
|
} = opt
|
|
93
120
|
|
|
121
|
+
const fakeError = keepStackTrace ? new Error('RetryError') : undefined
|
|
122
|
+
|
|
94
123
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt
|
|
95
124
|
|
|
96
125
|
if (opt.logAll) {
|
|
97
|
-
logFirstAttempt = logRetries = logFailures = true
|
|
126
|
+
logSuccess = logFirstAttempt = logRetries = logFailures = true
|
|
98
127
|
}
|
|
99
128
|
if (opt.logNone) {
|
|
100
129
|
logSuccess = logFirstAttempt = logRetries = logFailures = false
|
|
101
130
|
}
|
|
102
131
|
|
|
103
|
-
const fname =
|
|
132
|
+
const fname = name || fn.name || 'pRetry function'
|
|
104
133
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
134
|
+
let delay = initialDelay
|
|
135
|
+
let attempt = 0
|
|
136
|
+
let timer: NodeJS.Timeout | undefined
|
|
137
|
+
let timedOut = false
|
|
108
138
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
139
|
+
return await new Promise((resolve, reject) => {
|
|
140
|
+
const rejectWithTimeout = () => {
|
|
141
|
+
timedOut = true // to prevent more tries
|
|
142
|
+
const err = new TimeoutError(`"${fname}" timed out after ${timeout} ms`)
|
|
143
|
+
if (fakeError) {
|
|
144
|
+
// keep original stack
|
|
145
|
+
err.stack = fakeError.stack!.replace('Error: RetryError', 'TimeoutError')
|
|
146
|
+
}
|
|
147
|
+
reject(err)
|
|
148
|
+
}
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
116
|
-
logger.log(`${fname} attempt #${attempt}...`)
|
|
117
|
-
}
|
|
150
|
+
const next = async () => {
|
|
151
|
+
if (timedOut) return
|
|
118
152
|
|
|
119
|
-
|
|
153
|
+
if (timeout) {
|
|
154
|
+
timer = setTimeout(rejectWithTimeout, timeout)
|
|
155
|
+
}
|
|
120
156
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
}
|
|
157
|
+
const started = Date.now()
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
attempt++
|
|
161
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
162
|
+
logger.log(`${fname} attempt #${attempt}...`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const r = await fn(attempt)
|
|
166
|
+
|
|
167
|
+
clearTimeout(timer!)
|
|
134
168
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
if (logSuccess) {
|
|
170
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
resolve(r)
|
|
174
|
+
} catch (err) {
|
|
175
|
+
clearTimeout(timer!)
|
|
176
|
+
|
|
177
|
+
if (logFailures) {
|
|
178
|
+
logger.warn(
|
|
179
|
+
`${fname} attempt #${attempt} error in ${_since(started)}:`,
|
|
180
|
+
_stringifyAny(err, {
|
|
181
|
+
includeErrorData: true,
|
|
182
|
+
}),
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (
|
|
187
|
+
attempt >= maxAttempts ||
|
|
188
|
+
(predicate && !predicate(err as Error, attempt, maxAttempts))
|
|
189
|
+
) {
|
|
190
|
+
// Give up
|
|
191
|
+
|
|
192
|
+
if (fakeError) {
|
|
193
|
+
// Preserve the original call stack
|
|
194
|
+
Object.defineProperty(err, 'stack', {
|
|
195
|
+
value:
|
|
196
|
+
(err as Error).stack +
|
|
197
|
+
'\n --' +
|
|
198
|
+
fakeError.stack!.replace('Error: RetryError', ''),
|
|
199
|
+
})
|
|
142
200
|
}
|
|
201
|
+
|
|
202
|
+
reject(err)
|
|
203
|
+
} else {
|
|
204
|
+
// Retry after delay
|
|
205
|
+
delay *= delayMultiplier
|
|
206
|
+
setTimeout(next, delay)
|
|
143
207
|
}
|
|
144
208
|
}
|
|
209
|
+
}
|
|
145
210
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
} as any
|
|
211
|
+
void next()
|
|
212
|
+
})
|
|
149
213
|
}
|