@naturalcycles/js-lib 14.135.0 → 14.136.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/logMethod.decorator.js +2 -2
- package/dist/error/error.util.d.ts +1 -1
- package/dist/error/error.util.js +2 -0
- package/dist/error/tryCatch.js +1 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/promise/abortable.d.ts +20 -0
- package/dist/promise/abortable.js +36 -0
- package/dist/promise/pDefer.d.ts +14 -1
- package/dist/promise/pDefer.js +2 -0
- package/dist/promise/pDelay.d.ts +18 -0
- package/dist/promise/pDelay.js +37 -2
- package/dist/promise/pRetry.d.ts +0 -8
- package/dist/promise/pRetry.js +37 -63
- package/dist/promise/pTimeout.d.ts +4 -6
- package/dist/promise/pTimeout.js +8 -10
- package/dist/string/stringifyAny.d.ts +0 -6
- package/dist/string/stringifyAny.js +0 -5
- package/dist/types.d.ts +3 -0
- package/dist-esm/decorators/logMethod.decorator.js +2 -2
- package/dist-esm/error/error.util.js +2 -0
- package/dist-esm/error/tryCatch.js +2 -4
- package/dist-esm/index.js +1 -0
- package/dist-esm/promise/abortable.js +32 -0
- package/dist-esm/promise/pDefer.js +2 -0
- package/dist-esm/promise/pDelay.js +35 -1
- package/dist-esm/promise/pRetry.js +38 -61
- package/dist-esm/promise/pTimeout.js +8 -7
- package/dist-esm/string/stringifyAny.js +0 -5
- package/package.json +1 -1
- package/src/decorators/logMethod.decorator.ts +2 -2
- package/src/error/error.util.ts +3 -1
- package/src/error/tryCatch.ts +2 -6
- package/src/index.ts +1 -0
- package/src/promise/abortable.ts +34 -0
- package/src/promise/pDefer.ts +19 -1
- package/src/promise/pDelay.ts +44 -2
- package/src/promise/pRetry.ts +41 -89
- package/src/promise/pState.ts +1 -1
- package/src/promise/pTimeout.ts +12 -14
- package/src/string/stringifyAny.ts +0 -13
- package/src/types.ts +3 -0
|
@@ -80,10 +80,10 @@ function logFinished(logger, callSignature, started, sma, logResultFn, res, err)
|
|
|
80
80
|
t.push(`(avg ${(0, time_util_1._ms)(sma.push(millis))})`);
|
|
81
81
|
}
|
|
82
82
|
if (err !== undefined) {
|
|
83
|
-
t.push('ERROR:',
|
|
83
|
+
t.push('ERROR:', err);
|
|
84
84
|
}
|
|
85
85
|
else if (logResultFn) {
|
|
86
86
|
t.push(...logResultFn(res));
|
|
87
87
|
}
|
|
88
|
-
logger.log(t.filter(Boolean)
|
|
88
|
+
logger.log(...t.filter(Boolean));
|
|
89
89
|
}
|
package/dist/error/error.util.js
CHANGED
package/dist/error/tryCatch.js
CHANGED
|
@@ -24,9 +24,7 @@ function _tryCatch(fn, opt = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
26
|
if (logError) {
|
|
27
|
-
logger.warn(`tryCatch.${fname} error in ${(0, index_1._since)(started)}
|
|
28
|
-
includeErrorData: true,
|
|
29
|
-
})}`);
|
|
27
|
+
logger.warn(`tryCatch.${fname} error in ${(0, index_1._since)(started)}:`, err);
|
|
30
28
|
}
|
|
31
29
|
if (onError) {
|
|
32
30
|
try {
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export * from './unit/size.util';
|
|
|
61
61
|
export * from './log/commonLogger';
|
|
62
62
|
export * from './string/safeJsonStringify';
|
|
63
63
|
export * from './promise/pQueue';
|
|
64
|
+
export * from './promise/abortable';
|
|
64
65
|
export * from './seq/seq';
|
|
65
66
|
export * from './math/stack.util';
|
|
66
67
|
export * from './string/leven';
|
package/dist/index.js
CHANGED
|
@@ -65,6 +65,7 @@ tslib_1.__exportStar(require("./unit/size.util"), exports);
|
|
|
65
65
|
tslib_1.__exportStar(require("./log/commonLogger"), exports);
|
|
66
66
|
tslib_1.__exportStar(require("./string/safeJsonStringify"), exports);
|
|
67
67
|
tslib_1.__exportStar(require("./promise/pQueue"), exports);
|
|
68
|
+
tslib_1.__exportStar(require("./promise/abortable"), exports);
|
|
68
69
|
tslib_1.__exportStar(require("./seq/seq"), exports);
|
|
69
70
|
tslib_1.__exportStar(require("./math/stack.util"), exports);
|
|
70
71
|
tslib_1.__exportStar(require("./string/leven"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AnyFunction } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Similar to AbortController and AbortSignal.
|
|
4
|
+
* Similar to pDefer and Promise.
|
|
5
|
+
* Similar to Subject and Observable.
|
|
6
|
+
*
|
|
7
|
+
* Minimal interface for something that can be aborted in the future,
|
|
8
|
+
* but not necessary.
|
|
9
|
+
* Allows to listen to `onAbort` event.
|
|
10
|
+
*
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
export declare class Abortable {
|
|
14
|
+
onAbort?: AnyFunction<any> | undefined;
|
|
15
|
+
constructor(onAbort?: AnyFunction<any> | undefined);
|
|
16
|
+
aborted: boolean;
|
|
17
|
+
abort(): void;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function abortable(onAbort?: AnyFunction): Abortable;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.abortable = exports.Abortable = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Similar to AbortController and AbortSignal.
|
|
6
|
+
* Similar to pDefer and Promise.
|
|
7
|
+
* Similar to Subject and Observable.
|
|
8
|
+
*
|
|
9
|
+
* Minimal interface for something that can be aborted in the future,
|
|
10
|
+
* but not necessary.
|
|
11
|
+
* Allows to listen to `onAbort` event.
|
|
12
|
+
*
|
|
13
|
+
* @experimental
|
|
14
|
+
*/
|
|
15
|
+
class Abortable {
|
|
16
|
+
constructor(onAbort) {
|
|
17
|
+
this.onAbort = onAbort;
|
|
18
|
+
this.aborted = false;
|
|
19
|
+
}
|
|
20
|
+
abort() {
|
|
21
|
+
if (this.aborted)
|
|
22
|
+
return;
|
|
23
|
+
this.aborted = true;
|
|
24
|
+
this.onAbort?.();
|
|
25
|
+
this.onAbort = undefined; // cleanup listener
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
this.onAbort = undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.Abortable = Abortable;
|
|
32
|
+
// convenience function
|
|
33
|
+
function abortable(onAbort) {
|
|
34
|
+
return new Abortable(onAbort);
|
|
35
|
+
}
|
|
36
|
+
exports.abortable = abortable;
|
package/dist/promise/pDefer.d.ts
CHANGED
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface DeferredPromise<T = void> extends Promise<T> {
|
|
5
5
|
resolve: (a?: T) => void;
|
|
6
|
-
reject: (
|
|
6
|
+
reject: (err?: Error) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Can be overridden.
|
|
9
|
+
* Otherwise will reject with "Aborted" or "Aborted: $reason" on abort().
|
|
10
|
+
*
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
abort: (reason?: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Rejects the promise with `new Error('Aborted: $reason')`.
|
|
16
|
+
*
|
|
17
|
+
* @experimental
|
|
18
|
+
*/
|
|
19
|
+
rejectAborted: (reason?: string) => void;
|
|
7
20
|
}
|
|
8
21
|
/**
|
|
9
22
|
* Returns DeferredPromise - a Promise that has .resolve() and .reject() methods.
|
package/dist/promise/pDefer.js
CHANGED
|
@@ -14,6 +14,8 @@ function pDefer() {
|
|
|
14
14
|
});
|
|
15
15
|
promise.resolve = resolve;
|
|
16
16
|
promise.reject = reject;
|
|
17
|
+
promise.rejectAborted = reason => reject(new Error(['Aborted', reason].filter(Boolean).join(': ')));
|
|
18
|
+
promise.abort = reason => promise.rejectAborted(reason);
|
|
17
19
|
return promise;
|
|
18
20
|
}
|
|
19
21
|
exports.pDefer = pDefer;
|
package/dist/promise/pDelay.d.ts
CHANGED
|
@@ -1 +1,19 @@
|
|
|
1
|
+
import type { PromisableFunction } from '../types';
|
|
2
|
+
import { DeferredPromise } from './pDefer';
|
|
3
|
+
/**
|
|
4
|
+
* Promisified version of setTimeout.
|
|
5
|
+
*
|
|
6
|
+
* Can return a value.
|
|
7
|
+
* If value is instanceof Error - rejects the Promise instead of resolving.
|
|
8
|
+
*/
|
|
1
9
|
export declare function pDelay<T>(ms?: number, value?: T): Promise<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Promisified version of setTimeout.
|
|
12
|
+
*
|
|
13
|
+
* Wraps the passed function with try/catch,
|
|
14
|
+
* catch will propagate to pDelayFn rejection,
|
|
15
|
+
* otherwise pDelayFn will resolve with returned value.
|
|
16
|
+
*
|
|
17
|
+
* On abort() - clears the Timeout and immediately resolves the Promise with void.
|
|
18
|
+
*/
|
|
19
|
+
export declare function pDelayFn<T>(ms: number | undefined, fn: PromisableFunction<T>): DeferredPromise<T>;
|
package/dist/promise/pDelay.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pDelay = void 0;
|
|
3
|
+
exports.pDelayFn = exports.pDelay = void 0;
|
|
4
|
+
const pDefer_1 = require("./pDefer");
|
|
5
|
+
/**
|
|
6
|
+
* Promisified version of setTimeout.
|
|
7
|
+
*
|
|
8
|
+
* Can return a value.
|
|
9
|
+
* If value is instanceof Error - rejects the Promise instead of resolving.
|
|
10
|
+
*/
|
|
4
11
|
async function pDelay(ms = 0, value) {
|
|
5
|
-
return await new Promise(resolve => setTimeout(
|
|
12
|
+
return await new Promise((resolve, reject) => setTimeout(value instanceof Error ? reject : resolve, ms, value));
|
|
6
13
|
}
|
|
7
14
|
exports.pDelay = pDelay;
|
|
15
|
+
/* eslint-disable @typescript-eslint/promise-function-async */
|
|
16
|
+
/**
|
|
17
|
+
* Promisified version of setTimeout.
|
|
18
|
+
*
|
|
19
|
+
* Wraps the passed function with try/catch,
|
|
20
|
+
* catch will propagate to pDelayFn rejection,
|
|
21
|
+
* otherwise pDelayFn will resolve with returned value.
|
|
22
|
+
*
|
|
23
|
+
* On abort() - clears the Timeout and immediately resolves the Promise with void.
|
|
24
|
+
*/
|
|
25
|
+
function pDelayFn(ms = 0, fn) {
|
|
26
|
+
const p = (0, pDefer_1.pDefer)();
|
|
27
|
+
const timer = setTimeout(async () => {
|
|
28
|
+
try {
|
|
29
|
+
p.resolve(await fn());
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
p.reject(err);
|
|
33
|
+
}
|
|
34
|
+
}, ms);
|
|
35
|
+
p.abort = () => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
// p.rejectAborted(reason) // nope
|
|
38
|
+
p.resolve();
|
|
39
|
+
};
|
|
40
|
+
return p;
|
|
41
|
+
}
|
|
42
|
+
exports.pDelayFn = pDelayFn;
|
package/dist/promise/pRetry.d.ts
CHANGED
|
@@ -68,14 +68,6 @@ export interface PRetryOptions {
|
|
|
68
68
|
* Default to `console`
|
|
69
69
|
*/
|
|
70
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;
|
|
79
71
|
/**
|
|
80
72
|
* Will be merged with `err.data` object.
|
|
81
73
|
*/
|
package/dist/promise/pRetry.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pRetry = exports.pRetryFn = void 0;
|
|
4
4
|
const __1 = require("..");
|
|
5
|
-
const pTimeout_1 = require("./pTimeout");
|
|
6
5
|
/**
|
|
7
6
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
8
7
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
@@ -14,8 +13,8 @@ function pRetryFn(fn, opt = {}) {
|
|
|
14
13
|
}
|
|
15
14
|
exports.pRetryFn = pRetryFn;
|
|
16
15
|
async function pRetry(fn, opt = {}) {
|
|
17
|
-
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name,
|
|
18
|
-
const fakeError =
|
|
16
|
+
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, timeout, } = opt;
|
|
17
|
+
const fakeError = timeout ? new Error('TimeoutError') : undefined;
|
|
19
18
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
|
|
20
19
|
if (opt.logAll) {
|
|
21
20
|
logSuccess = logFirstAttempt = logRetries = logFailures = true;
|
|
@@ -26,70 +25,45 @@ async function pRetry(fn, opt = {}) {
|
|
|
26
25
|
const fname = name || fn.name || 'pRetry function';
|
|
27
26
|
let delay = initialDelay;
|
|
28
27
|
let attempt = 0;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// keep original stack
|
|
37
|
-
err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
|
|
28
|
+
/* eslint-disable no-await-in-loop, no-constant-condition */
|
|
29
|
+
while (true) {
|
|
30
|
+
const started = Date.now();
|
|
31
|
+
try {
|
|
32
|
+
attempt++;
|
|
33
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
34
|
+
logger.log(`${fname} attempt #${attempt}...`);
|
|
38
35
|
}
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
const next = async () => {
|
|
42
|
-
if (timedOut)
|
|
43
|
-
return;
|
|
36
|
+
let result;
|
|
44
37
|
if (timeout) {
|
|
45
|
-
|
|
38
|
+
await (0, __1.pTimeout)(async () => await fn(attempt), {
|
|
39
|
+
timeout,
|
|
40
|
+
name: fname,
|
|
41
|
+
errorData: opt.errorData,
|
|
42
|
+
fakeError,
|
|
43
|
+
});
|
|
46
44
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
attempt++;
|
|
50
|
-
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
51
|
-
logger.log(`${fname} attempt #${attempt}...`);
|
|
52
|
-
}
|
|
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);
|
|
45
|
+
else {
|
|
46
|
+
result = await fn(attempt);
|
|
59
47
|
}
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
});
|
|
77
|
-
}
|
|
78
|
-
;
|
|
79
|
-
err.data = {
|
|
80
|
-
...err.data,
|
|
81
|
-
...opt.errorData,
|
|
82
|
-
};
|
|
83
|
-
reject(err);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// Retry after delay
|
|
87
|
-
delay *= delayMultiplier;
|
|
88
|
-
setTimeout(next, delay);
|
|
89
|
-
}
|
|
48
|
+
if (logSuccess) {
|
|
49
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${(0, __1._since)(started)}`);
|
|
90
50
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (logFailures) {
|
|
55
|
+
logger.warn(`${fname} attempt #${attempt} error in ${(0, __1._since)(started)}:`, err);
|
|
56
|
+
}
|
|
57
|
+
if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
|
|
58
|
+
// Give up
|
|
59
|
+
(0, __1._errorDataAppend)(err, opt.errorData);
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
// Retry after delay
|
|
63
|
+
delay *= delayMultiplier;
|
|
64
|
+
await (0, __1.pDelay)(delay);
|
|
65
|
+
// back to while(true) loop
|
|
66
|
+
}
|
|
67
|
+
}
|
|
94
68
|
}
|
|
95
69
|
exports.pRetry = pRetry;
|
|
@@ -23,13 +23,11 @@ export interface PTimeoutOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
onTimeout?: (err: TimeoutError) => any;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @experimental
|
|
26
|
+
* If passed - fakeError.stack will be used as a stacktrace.
|
|
27
|
+
* This is to "keep stacktrace" when pTimeout is called from another
|
|
28
|
+
* function (like pRetry).
|
|
31
29
|
*/
|
|
32
|
-
|
|
30
|
+
fakeError?: Error;
|
|
33
31
|
/**
|
|
34
32
|
* Will be merged with `err.data` object.
|
|
35
33
|
*/
|
package/dist/promise/pTimeout.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.pTimeout = exports.pTimeoutFn = exports.TimeoutError = void 0;
|
|
4
4
|
const app_error_1 = require("../error/app.error");
|
|
5
|
+
const error_util_1 = require("../error/error.util");
|
|
5
6
|
class TimeoutError extends app_error_1.AppError {
|
|
6
7
|
constructor(message, data = {}, opt) {
|
|
7
8
|
super(message, data, opt, 'TimeoutError');
|
|
@@ -29,26 +30,23 @@ exports.pTimeoutFn = pTimeoutFn;
|
|
|
29
30
|
* If the Function rejects - passes this rejection further.
|
|
30
31
|
*/
|
|
31
32
|
async function pTimeout(fn, opt) {
|
|
32
|
-
const { timeout, name = fn.name || 'pTimeout function', onTimeout
|
|
33
|
-
const fakeError =
|
|
33
|
+
const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt;
|
|
34
|
+
const fakeError = opt.fakeError || new Error('TimeoutError');
|
|
34
35
|
// eslint-disable-next-line no-async-promise-executor
|
|
35
36
|
return await new Promise(async (resolve, reject) => {
|
|
36
37
|
// Prepare the timeout timer
|
|
37
38
|
const timer = setTimeout(() => {
|
|
38
39
|
const err = new TimeoutError(`"${name}" timed out after ${timeout} ms`, opt.errorData);
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
// keep original stack
|
|
41
|
+
err.stack = fakeError.stack.replace('Error: TimeoutError', 'TimeoutError: ' + err.message);
|
|
41
42
|
if (onTimeout) {
|
|
42
43
|
try {
|
|
43
44
|
resolve(onTimeout(err));
|
|
44
45
|
}
|
|
45
46
|
catch (err) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
err.
|
|
49
|
-
...err.data,
|
|
50
|
-
...opt.errorData,
|
|
51
|
-
};
|
|
47
|
+
// keep original stack
|
|
48
|
+
err.stack = fakeError.stack.replace('Error: TimeoutError', err.name + ': ' + err.message);
|
|
49
|
+
(0, error_util_1._errorDataAppend)(err, opt.errorData);
|
|
52
50
|
reject(err);
|
|
53
51
|
}
|
|
54
52
|
return;
|
|
@@ -20,12 +20,6 @@ export interface StringifyAnyOptions {
|
|
|
20
20
|
* Default limit is less than in Node, cause it's likely to be used e.g in Browser alert()
|
|
21
21
|
*/
|
|
22
22
|
maxLen?: number;
|
|
23
|
-
/**
|
|
24
|
-
* Pass true to include "stringified" `error.data` in the output.
|
|
25
|
-
*
|
|
26
|
-
* @default false
|
|
27
|
-
*/
|
|
28
|
-
includeErrorData?: boolean;
|
|
29
23
|
/**
|
|
30
24
|
* Set to true to print Error.stack instead of just Error.message.
|
|
31
25
|
*
|
|
@@ -86,11 +86,6 @@ function _stringifyAny(obj, opt = {}) {
|
|
|
86
86
|
// `replace` here works ONCE, exactly as we need it
|
|
87
87
|
s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
|
|
88
88
|
}
|
|
89
|
-
// Here we ensure it has `data`
|
|
90
|
-
const { data } = obj;
|
|
91
|
-
if (opt.includeErrorData && Object.keys(data).length > 0) {
|
|
92
|
-
s = [s, _stringifyAny(data, opt)].join('\n');
|
|
93
|
-
}
|
|
94
89
|
}
|
|
95
90
|
else if (typeof obj.code === 'string') {
|
|
96
91
|
// Error that has no `data`, but has `code` property
|
package/dist/types.d.ts
CHANGED
|
@@ -59,6 +59,9 @@ export type UnsavedId<T extends Partial<ObjectWithId>> = Omit<T, 'id'> & {
|
|
|
59
59
|
*/
|
|
60
60
|
export type AnyFunction<T = any> = (...args: any[]) => T;
|
|
61
61
|
export type AnyAsyncFunction<T = any> = (...args: any[]) => Promise<T>;
|
|
62
|
+
export type AsyncFunction<T = any> = () => Promise<T>;
|
|
63
|
+
export type AnyPromisableFunction<T = any> = (...args: any[]) => Promisable<T>;
|
|
64
|
+
export type PromisableFunction<T = any> = () => Promisable<T>;
|
|
62
65
|
/**
|
|
63
66
|
* Symbol to indicate END of Sequence.
|
|
64
67
|
*/
|
|
@@ -76,10 +76,10 @@ function logFinished(logger, callSignature, started, sma, logResultFn, res, err)
|
|
|
76
76
|
t.push(`(avg ${_ms(sma.push(millis))})`);
|
|
77
77
|
}
|
|
78
78
|
if (err !== undefined) {
|
|
79
|
-
t.push('ERROR:',
|
|
79
|
+
t.push('ERROR:', err);
|
|
80
80
|
}
|
|
81
81
|
else if (logResultFn) {
|
|
82
82
|
t.push(...logResultFn(res));
|
|
83
83
|
}
|
|
84
|
-
logger.log(t.filter(Boolean)
|
|
84
|
+
logger.log(...t.filter(Boolean));
|
|
85
85
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _anyToError, _since
|
|
1
|
+
import { _anyToError, _since } from '../index';
|
|
2
2
|
/**
|
|
3
3
|
* Decorates a function with "try/catch", so it'll never reject/throw.
|
|
4
4
|
* Only applies to async functions (or, turns sync function into async).
|
|
@@ -21,9 +21,7 @@ export function _tryCatch(fn, opt = {}) {
|
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
23
23
|
if (logError) {
|
|
24
|
-
logger.warn(`tryCatch.${fname} error in ${_since(started)}
|
|
25
|
-
includeErrorData: true,
|
|
26
|
-
})}`);
|
|
24
|
+
logger.warn(`tryCatch.${fname} error in ${_since(started)}:`, err);
|
|
27
25
|
}
|
|
28
26
|
if (onError) {
|
|
29
27
|
try {
|
package/dist-esm/index.js
CHANGED
|
@@ -61,6 +61,7 @@ export * from './unit/size.util';
|
|
|
61
61
|
export * from './log/commonLogger';
|
|
62
62
|
export * from './string/safeJsonStringify';
|
|
63
63
|
export * from './promise/pQueue';
|
|
64
|
+
export * from './promise/abortable';
|
|
64
65
|
export * from './seq/seq';
|
|
65
66
|
export * from './math/stack.util';
|
|
66
67
|
export * from './string/leven';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similar to AbortController and AbortSignal.
|
|
3
|
+
* Similar to pDefer and Promise.
|
|
4
|
+
* Similar to Subject and Observable.
|
|
5
|
+
*
|
|
6
|
+
* Minimal interface for something that can be aborted in the future,
|
|
7
|
+
* but not necessary.
|
|
8
|
+
* Allows to listen to `onAbort` event.
|
|
9
|
+
*
|
|
10
|
+
* @experimental
|
|
11
|
+
*/
|
|
12
|
+
export class Abortable {
|
|
13
|
+
constructor(onAbort) {
|
|
14
|
+
this.onAbort = onAbort;
|
|
15
|
+
this.aborted = false;
|
|
16
|
+
}
|
|
17
|
+
abort() {
|
|
18
|
+
var _a;
|
|
19
|
+
if (this.aborted)
|
|
20
|
+
return;
|
|
21
|
+
this.aborted = true;
|
|
22
|
+
(_a = this.onAbort) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
23
|
+
this.onAbort = undefined; // cleanup listener
|
|
24
|
+
}
|
|
25
|
+
clear() {
|
|
26
|
+
this.onAbort = undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// convenience function
|
|
30
|
+
export function abortable(onAbort) {
|
|
31
|
+
return new Abortable(onAbort);
|
|
32
|
+
}
|
|
@@ -11,5 +11,7 @@ export function pDefer() {
|
|
|
11
11
|
});
|
|
12
12
|
promise.resolve = resolve;
|
|
13
13
|
promise.reject = reject;
|
|
14
|
+
promise.rejectAborted = reason => reject(new Error(['Aborted', reason].filter(Boolean).join(': ')));
|
|
15
|
+
promise.abort = reason => promise.rejectAborted(reason);
|
|
14
16
|
return promise;
|
|
15
17
|
}
|
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
import { pDefer } from './pDefer';
|
|
2
|
+
/**
|
|
3
|
+
* Promisified version of setTimeout.
|
|
4
|
+
*
|
|
5
|
+
* Can return a value.
|
|
6
|
+
* If value is instanceof Error - rejects the Promise instead of resolving.
|
|
7
|
+
*/
|
|
1
8
|
export async function pDelay(ms = 0, value) {
|
|
2
|
-
return await new Promise(resolve => setTimeout(
|
|
9
|
+
return await new Promise((resolve, reject) => setTimeout(value instanceof Error ? reject : resolve, ms, value));
|
|
10
|
+
}
|
|
11
|
+
/* eslint-disable @typescript-eslint/promise-function-async */
|
|
12
|
+
/**
|
|
13
|
+
* Promisified version of setTimeout.
|
|
14
|
+
*
|
|
15
|
+
* Wraps the passed function with try/catch,
|
|
16
|
+
* catch will propagate to pDelayFn rejection,
|
|
17
|
+
* otherwise pDelayFn will resolve with returned value.
|
|
18
|
+
*
|
|
19
|
+
* On abort() - clears the Timeout and immediately resolves the Promise with void.
|
|
20
|
+
*/
|
|
21
|
+
export function pDelayFn(ms = 0, fn) {
|
|
22
|
+
const p = pDefer();
|
|
23
|
+
const timer = setTimeout(async () => {
|
|
24
|
+
try {
|
|
25
|
+
p.resolve(await fn());
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
p.reject(err);
|
|
29
|
+
}
|
|
30
|
+
}, ms);
|
|
31
|
+
p.abort = () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
// p.rejectAborted(reason) // nope
|
|
34
|
+
p.resolve();
|
|
35
|
+
};
|
|
36
|
+
return p;
|
|
3
37
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { _since,
|
|
2
|
-
import { TimeoutError } from './pTimeout';
|
|
1
|
+
import { _errorDataAppend, _since, pDelay, pTimeout } from '..';
|
|
3
2
|
/**
|
|
4
3
|
* Returns a Function (!), enhanced with retry capabilities.
|
|
5
4
|
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
|
|
@@ -10,8 +9,8 @@ export function pRetryFn(fn, opt = {}) {
|
|
|
10
9
|
};
|
|
11
10
|
}
|
|
12
11
|
export async function pRetry(fn, opt = {}) {
|
|
13
|
-
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name,
|
|
14
|
-
const fakeError =
|
|
12
|
+
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, timeout, } = opt;
|
|
13
|
+
const fakeError = timeout ? new Error('TimeoutError') : undefined;
|
|
15
14
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt;
|
|
16
15
|
if (opt.logAll) {
|
|
17
16
|
logSuccess = logFirstAttempt = logRetries = logFailures = true;
|
|
@@ -22,66 +21,44 @@ export async function pRetry(fn, opt = {}) {
|
|
|
22
21
|
const fname = name || fn.name || 'pRetry function';
|
|
23
22
|
let delay = initialDelay;
|
|
24
23
|
let attempt = 0;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// keep original stack
|
|
33
|
-
err.stack = fakeError.stack.replace('Error: RetryError', 'TimeoutError');
|
|
24
|
+
/* eslint-disable no-await-in-loop, no-constant-condition */
|
|
25
|
+
while (true) {
|
|
26
|
+
const started = Date.now();
|
|
27
|
+
try {
|
|
28
|
+
attempt++;
|
|
29
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
30
|
+
logger.log(`${fname} attempt #${attempt}...`);
|
|
34
31
|
}
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
const next = async () => {
|
|
38
|
-
if (timedOut)
|
|
39
|
-
return;
|
|
32
|
+
let result;
|
|
40
33
|
if (timeout) {
|
|
41
|
-
|
|
34
|
+
await pTimeout(async () => await fn(attempt), {
|
|
35
|
+
timeout,
|
|
36
|
+
name: fname,
|
|
37
|
+
errorData: opt.errorData,
|
|
38
|
+
fakeError,
|
|
39
|
+
});
|
|
42
40
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
attempt++;
|
|
46
|
-
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
47
|
-
logger.log(`${fname} attempt #${attempt}...`);
|
|
48
|
-
}
|
|
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);
|
|
41
|
+
else {
|
|
42
|
+
result = await fn(attempt);
|
|
55
43
|
}
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
});
|
|
73
|
-
}
|
|
74
|
-
;
|
|
75
|
-
err.data = Object.assign(Object.assign({}, err.data), opt.errorData);
|
|
76
|
-
reject(err);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
// Retry after delay
|
|
80
|
-
delay *= delayMultiplier;
|
|
81
|
-
setTimeout(next, delay);
|
|
82
|
-
}
|
|
44
|
+
if (logSuccess) {
|
|
45
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`);
|
|
83
46
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (logFailures) {
|
|
51
|
+
logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, err);
|
|
52
|
+
}
|
|
53
|
+
if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
|
|
54
|
+
// Give up
|
|
55
|
+
_errorDataAppend(err, opt.errorData);
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
// Retry after delay
|
|
59
|
+
delay *= delayMultiplier;
|
|
60
|
+
await pDelay(delay);
|
|
61
|
+
// back to while(true) loop
|
|
62
|
+
}
|
|
63
|
+
}
|
|
87
64
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AppError } from '../error/app.error';
|
|
2
|
+
import { _errorDataAppend } from '../error/error.util';
|
|
2
3
|
export class TimeoutError extends AppError {
|
|
3
4
|
constructor(message, data = {}, opt) {
|
|
4
5
|
super(message, data, opt, 'TimeoutError');
|
|
@@ -24,23 +25,23 @@ export function pTimeoutFn(fn, opt) {
|
|
|
24
25
|
* If the Function rejects - passes this rejection further.
|
|
25
26
|
*/
|
|
26
27
|
export async function pTimeout(fn, opt) {
|
|
27
|
-
const { timeout, name = fn.name || 'pTimeout function', onTimeout
|
|
28
|
-
const fakeError =
|
|
28
|
+
const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt;
|
|
29
|
+
const fakeError = opt.fakeError || new Error('TimeoutError');
|
|
29
30
|
// eslint-disable-next-line no-async-promise-executor
|
|
30
31
|
return await new Promise(async (resolve, reject) => {
|
|
31
32
|
// Prepare the timeout timer
|
|
32
33
|
const timer = setTimeout(() => {
|
|
33
34
|
const err = new TimeoutError(`"${name}" timed out after ${timeout} ms`, opt.errorData);
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
// keep original stack
|
|
36
|
+
err.stack = fakeError.stack.replace('Error: TimeoutError', 'TimeoutError: ' + err.message);
|
|
36
37
|
if (onTimeout) {
|
|
37
38
|
try {
|
|
38
39
|
resolve(onTimeout(err));
|
|
39
40
|
}
|
|
40
41
|
catch (err) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// keep original stack
|
|
43
|
+
err.stack = fakeError.stack.replace('Error: TimeoutError', err.name + ': ' + err.message);
|
|
44
|
+
_errorDataAppend(err, opt.errorData);
|
|
44
45
|
reject(err);
|
|
45
46
|
}
|
|
46
47
|
return;
|
|
@@ -82,11 +82,6 @@ export function _stringifyAny(obj, opt = {}) {
|
|
|
82
82
|
// `replace` here works ONCE, exactly as we need it
|
|
83
83
|
s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`);
|
|
84
84
|
}
|
|
85
|
-
// Here we ensure it has `data`
|
|
86
|
-
const { data } = obj;
|
|
87
|
-
if (opt.includeErrorData && Object.keys(data).length > 0) {
|
|
88
|
-
s = [s, _stringifyAny(data, opt)].join('\n');
|
|
89
|
-
}
|
|
90
85
|
}
|
|
91
86
|
else if (typeof obj.code === 'string') {
|
|
92
87
|
// Error that has no `data`, but has `code` property
|
package/package.json
CHANGED
|
@@ -155,10 +155,10 @@ function logFinished(
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (err !== undefined) {
|
|
158
|
-
t.push('ERROR:',
|
|
158
|
+
t.push('ERROR:', err)
|
|
159
159
|
} else if (logResultFn) {
|
|
160
160
|
t.push(...logResultFn(res))
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
logger.log(t.filter(Boolean)
|
|
163
|
+
logger.log(...t.filter(Boolean))
|
|
164
164
|
}
|
package/src/error/error.util.ts
CHANGED
|
@@ -181,7 +181,9 @@ export function _isErrorObject(o: any): o is ErrorObject {
|
|
|
181
181
|
* })
|
|
182
182
|
* }
|
|
183
183
|
*/
|
|
184
|
-
export function _errorDataAppend(err: any, data
|
|
184
|
+
export function _errorDataAppend(err: any, data?: ErrorData): void {
|
|
185
|
+
if (!data) return
|
|
186
|
+
|
|
185
187
|
err.data = {
|
|
186
188
|
...err.data,
|
|
187
189
|
...data,
|
package/src/error/tryCatch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommonLogger } from '../index'
|
|
2
|
-
import { _anyToError, _since
|
|
2
|
+
import { _anyToError, _since } from '../index'
|
|
3
3
|
import type { AnyFunction } from '../types'
|
|
4
4
|
|
|
5
5
|
export interface TryCatchOptions {
|
|
@@ -51,11 +51,7 @@ export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {
|
|
|
51
51
|
return r
|
|
52
52
|
} catch (err) {
|
|
53
53
|
if (logError) {
|
|
54
|
-
logger.warn(
|
|
55
|
-
`tryCatch.${fname} error in ${_since(started)}:\n${_stringifyAny(err, {
|
|
56
|
-
includeErrorData: true,
|
|
57
|
-
})}`,
|
|
58
|
-
)
|
|
54
|
+
logger.warn(`tryCatch.${fname} error in ${_since(started)}:`, err)
|
|
59
55
|
}
|
|
60
56
|
|
|
61
57
|
if (onError) {
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export * from './unit/size.util'
|
|
|
61
61
|
export * from './log/commonLogger'
|
|
62
62
|
export * from './string/safeJsonStringify'
|
|
63
63
|
export * from './promise/pQueue'
|
|
64
|
+
export * from './promise/abortable'
|
|
64
65
|
export * from './seq/seq'
|
|
65
66
|
export * from './math/stack.util'
|
|
66
67
|
export * from './string/leven'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AnyFunction } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Similar to AbortController and AbortSignal.
|
|
5
|
+
* Similar to pDefer and Promise.
|
|
6
|
+
* Similar to Subject and Observable.
|
|
7
|
+
*
|
|
8
|
+
* Minimal interface for something that can be aborted in the future,
|
|
9
|
+
* but not necessary.
|
|
10
|
+
* Allows to listen to `onAbort` event.
|
|
11
|
+
*
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
export class Abortable {
|
|
15
|
+
constructor(public onAbort?: AnyFunction) {}
|
|
16
|
+
|
|
17
|
+
aborted = false
|
|
18
|
+
|
|
19
|
+
abort(): void {
|
|
20
|
+
if (this.aborted) return
|
|
21
|
+
this.aborted = true
|
|
22
|
+
this.onAbort?.()
|
|
23
|
+
this.onAbort = undefined // cleanup listener
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clear(): void {
|
|
27
|
+
this.onAbort = undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// convenience function
|
|
32
|
+
export function abortable(onAbort?: AnyFunction): Abortable {
|
|
33
|
+
return new Abortable(onAbort)
|
|
34
|
+
}
|
package/src/promise/pDefer.ts
CHANGED
|
@@ -3,7 +3,22 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface DeferredPromise<T = void> extends Promise<T> {
|
|
5
5
|
resolve: (a?: T) => void
|
|
6
|
-
reject: (
|
|
6
|
+
reject: (err?: Error) => void
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Can be overridden.
|
|
10
|
+
* Otherwise will reject with "Aborted" or "Aborted: $reason" on abort().
|
|
11
|
+
*
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
abort: (reason?: string) => void
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Rejects the promise with `new Error('Aborted: $reason')`.
|
|
18
|
+
*
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
rejectAborted: (reason?: string) => void
|
|
7
22
|
}
|
|
8
23
|
|
|
9
24
|
/* eslint-disable @typescript-eslint/promise-function-async */
|
|
@@ -22,6 +37,9 @@ export function pDefer<T = void>(): DeferredPromise<T> {
|
|
|
22
37
|
|
|
23
38
|
promise.resolve = resolve
|
|
24
39
|
promise.reject = reject
|
|
40
|
+
promise.rejectAborted = reason =>
|
|
41
|
+
reject(new Error(['Aborted', reason].filter(Boolean).join(': ')))
|
|
42
|
+
promise.abort = reason => promise.rejectAborted(reason)
|
|
25
43
|
|
|
26
44
|
return promise
|
|
27
45
|
}
|
package/src/promise/pDelay.ts
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { PromisableFunction } from '../types'
|
|
2
|
+
import { DeferredPromise, pDefer } from './pDefer'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Promisified version of setTimeout.
|
|
6
|
+
*
|
|
7
|
+
* Can return a value.
|
|
8
|
+
* If value is instanceof Error - rejects the Promise instead of resolving.
|
|
9
|
+
*/
|
|
10
|
+
export async function pDelay<T>(ms = 0, value?: T): Promise<T> {
|
|
11
|
+
return await new Promise<T>((resolve, reject) =>
|
|
12
|
+
setTimeout(value instanceof Error ? reject : resolve, ms, value),
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* eslint-disable @typescript-eslint/promise-function-async */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Promisified version of setTimeout.
|
|
20
|
+
*
|
|
21
|
+
* Wraps the passed function with try/catch,
|
|
22
|
+
* catch will propagate to pDelayFn rejection,
|
|
23
|
+
* otherwise pDelayFn will resolve with returned value.
|
|
24
|
+
*
|
|
25
|
+
* On abort() - clears the Timeout and immediately resolves the Promise with void.
|
|
26
|
+
*/
|
|
27
|
+
export function pDelayFn<T>(ms = 0, fn: PromisableFunction<T>): DeferredPromise<T> {
|
|
28
|
+
const p = pDefer<T>()
|
|
29
|
+
|
|
30
|
+
const timer = setTimeout(async () => {
|
|
31
|
+
try {
|
|
32
|
+
p.resolve(await fn())
|
|
33
|
+
} catch (err) {
|
|
34
|
+
p.reject(err as Error)
|
|
35
|
+
}
|
|
36
|
+
}, ms)
|
|
37
|
+
|
|
38
|
+
p.abort = () => {
|
|
39
|
+
clearTimeout(timer)
|
|
40
|
+
// p.rejectAborted(reason) // nope
|
|
41
|
+
p.resolve()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return p
|
|
3
45
|
}
|
package/src/promise/pRetry.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { AnyFunction,
|
|
2
|
-
import { _since,
|
|
3
|
-
import { TimeoutError } from './pTimeout'
|
|
1
|
+
import type { AnyFunction, CommonLogger, ErrorData } from '..'
|
|
2
|
+
import { _errorDataAppend, _since, pDelay, pTimeout } from '..'
|
|
4
3
|
|
|
5
4
|
export interface PRetryOptions {
|
|
6
5
|
/**
|
|
@@ -84,15 +83,6 @@ export interface PRetryOptions {
|
|
|
84
83
|
*/
|
|
85
84
|
logger?: CommonLogger
|
|
86
85
|
|
|
87
|
-
/**
|
|
88
|
-
* Defaults to true.
|
|
89
|
-
* If true - preserves the stack trace in case of a Timeout (usually - very useful!).
|
|
90
|
-
* It has a certain perf cost.
|
|
91
|
-
*
|
|
92
|
-
* @experimental
|
|
93
|
-
*/
|
|
94
|
-
keepStackTrace?: boolean
|
|
95
|
-
|
|
96
86
|
/**
|
|
97
87
|
* Will be merged with `err.data` object.
|
|
98
88
|
*/
|
|
@@ -120,12 +110,10 @@ export async function pRetry<T>(
|
|
|
120
110
|
predicate,
|
|
121
111
|
logger = console,
|
|
122
112
|
name,
|
|
123
|
-
keepStackTrace = true,
|
|
124
113
|
timeout,
|
|
125
114
|
} = opt
|
|
126
115
|
|
|
127
|
-
const fakeError =
|
|
128
|
-
|
|
116
|
+
const fakeError = timeout ? new Error('TimeoutError') : undefined
|
|
129
117
|
let { logFirstAttempt = false, logRetries = true, logFailures = false, logSuccess = false } = opt
|
|
130
118
|
|
|
131
119
|
if (opt.logAll) {
|
|
@@ -139,86 +127,50 @@ export async function pRetry<T>(
|
|
|
139
127
|
|
|
140
128
|
let delay = initialDelay
|
|
141
129
|
let attempt = 0
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
err.stack = fakeError.stack!.replace('Error: RetryError', 'TimeoutError')
|
|
130
|
+
|
|
131
|
+
/* eslint-disable no-await-in-loop, no-constant-condition */
|
|
132
|
+
while (true) {
|
|
133
|
+
const started = Date.now()
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
attempt++
|
|
137
|
+
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
|
|
138
|
+
logger.log(`${fname} attempt #${attempt}...`)
|
|
152
139
|
}
|
|
153
|
-
reject(err)
|
|
154
|
-
}
|
|
155
140
|
|
|
156
|
-
|
|
157
|
-
if (timedOut) return
|
|
141
|
+
let result: any
|
|
158
142
|
|
|
159
143
|
if (timeout) {
|
|
160
|
-
|
|
144
|
+
await pTimeout(async () => await fn(attempt), {
|
|
145
|
+
timeout,
|
|
146
|
+
name: fname,
|
|
147
|
+
errorData: opt.errorData,
|
|
148
|
+
fakeError,
|
|
149
|
+
})
|
|
150
|
+
} else {
|
|
151
|
+
result = await fn(attempt)
|
|
161
152
|
}
|
|
162
153
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const r = await fn(attempt)
|
|
172
|
-
|
|
173
|
-
clearTimeout(timer)
|
|
174
|
-
|
|
175
|
-
if (logSuccess) {
|
|
176
|
-
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
resolve(r)
|
|
180
|
-
} catch (err) {
|
|
181
|
-
clearTimeout(timer)
|
|
182
|
-
|
|
183
|
-
if (logFailures) {
|
|
184
|
-
logger.warn(
|
|
185
|
-
`${fname} attempt #${attempt} error in ${_since(started)}:`,
|
|
186
|
-
_stringifyAny(err, {
|
|
187
|
-
includeErrorData: true,
|
|
188
|
-
}),
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (
|
|
193
|
-
attempt >= maxAttempts ||
|
|
194
|
-
(predicate && !predicate(err as Error, attempt, maxAttempts))
|
|
195
|
-
) {
|
|
196
|
-
// Give up
|
|
197
|
-
|
|
198
|
-
if (fakeError) {
|
|
199
|
-
// Preserve the original call stack
|
|
200
|
-
Object.defineProperty(err, 'stack', {
|
|
201
|
-
value:
|
|
202
|
-
(err as Error).stack +
|
|
203
|
-
'\n --' +
|
|
204
|
-
fakeError.stack!.replace('Error: RetryError', ''),
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
;(err as AppError).data = {
|
|
209
|
-
...(err as AppError).data,
|
|
210
|
-
...opt.errorData,
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
reject(err)
|
|
214
|
-
} else {
|
|
215
|
-
// Retry after delay
|
|
216
|
-
delay *= delayMultiplier
|
|
217
|
-
setTimeout(next, delay)
|
|
218
|
-
}
|
|
154
|
+
if (logSuccess) {
|
|
155
|
+
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (logFailures) {
|
|
161
|
+
logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, err)
|
|
219
162
|
}
|
|
220
|
-
}
|
|
221
163
|
|
|
222
|
-
|
|
223
|
-
|
|
164
|
+
if (attempt >= maxAttempts || (predicate && !predicate(err as Error, attempt, maxAttempts))) {
|
|
165
|
+
// Give up
|
|
166
|
+
_errorDataAppend(err, opt.errorData)
|
|
167
|
+
throw err
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Retry after delay
|
|
171
|
+
delay *= delayMultiplier
|
|
172
|
+
await pDelay(delay)
|
|
173
|
+
// back to while(true) loop
|
|
174
|
+
}
|
|
175
|
+
}
|
|
224
176
|
}
|
package/src/promise/pState.ts
CHANGED
package/src/promise/pTimeout.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AppError } from '../error/app.error'
|
|
2
2
|
import type { ErrorData } from '../error/error.model'
|
|
3
|
+
import { _errorDataAppend } from '../error/error.util'
|
|
3
4
|
import type { AnyAsyncFunction } from '../types'
|
|
4
5
|
|
|
5
6
|
export class TimeoutError extends AppError {
|
|
@@ -30,13 +31,11 @@ export interface PTimeoutOptions {
|
|
|
30
31
|
onTimeout?: (err: TimeoutError) => any
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @experimental
|
|
34
|
+
* If passed - fakeError.stack will be used as a stacktrace.
|
|
35
|
+
* This is to "keep stacktrace" when pTimeout is called from another
|
|
36
|
+
* function (like pRetry).
|
|
38
37
|
*/
|
|
39
|
-
|
|
38
|
+
fakeError?: Error
|
|
40
39
|
|
|
41
40
|
/**
|
|
42
41
|
* Will be merged with `err.data` object.
|
|
@@ -66,25 +65,24 @@ export function pTimeoutFn<T extends AnyAsyncFunction>(fn: T, opt: PTimeoutOptio
|
|
|
66
65
|
* If the Function rejects - passes this rejection further.
|
|
67
66
|
*/
|
|
68
67
|
export async function pTimeout<T>(fn: AnyAsyncFunction<T>, opt: PTimeoutOptions): Promise<T> {
|
|
69
|
-
const { timeout, name = fn.name || 'pTimeout function', onTimeout
|
|
70
|
-
const fakeError =
|
|
68
|
+
const { timeout, name = fn.name || 'pTimeout function', onTimeout } = opt
|
|
69
|
+
const fakeError = opt.fakeError || new Error('TimeoutError')
|
|
71
70
|
|
|
72
71
|
// eslint-disable-next-line no-async-promise-executor
|
|
73
72
|
return await new Promise(async (resolve, reject) => {
|
|
74
73
|
// Prepare the timeout timer
|
|
75
74
|
const timer = setTimeout(() => {
|
|
76
75
|
const err = new TimeoutError(`"${name}" timed out after ${timeout} ms`, opt.errorData)
|
|
77
|
-
|
|
76
|
+
// keep original stack
|
|
77
|
+
err.stack = fakeError.stack!.replace('Error: TimeoutError', 'TimeoutError: ' + err.message)
|
|
78
78
|
|
|
79
79
|
if (onTimeout) {
|
|
80
80
|
try {
|
|
81
81
|
resolve(onTimeout(err))
|
|
82
82
|
} catch (err: any) {
|
|
83
|
-
|
|
84
|
-
err.
|
|
85
|
-
|
|
86
|
-
...opt.errorData,
|
|
87
|
-
}
|
|
83
|
+
// keep original stack
|
|
84
|
+
err.stack = fakeError.stack!.replace('Error: TimeoutError', err.name + ': ' + err.message)
|
|
85
|
+
_errorDataAppend(err, opt.errorData)
|
|
88
86
|
reject(err)
|
|
89
87
|
}
|
|
90
88
|
return
|
|
@@ -33,13 +33,6 @@ export interface StringifyAnyOptions {
|
|
|
33
33
|
*/
|
|
34
34
|
maxLen?: number
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Pass true to include "stringified" `error.data` in the output.
|
|
38
|
-
*
|
|
39
|
-
* @default false
|
|
40
|
-
*/
|
|
41
|
-
includeErrorData?: boolean
|
|
42
|
-
|
|
43
36
|
/**
|
|
44
37
|
* Set to true to print Error.stack instead of just Error.message.
|
|
45
38
|
*
|
|
@@ -130,12 +123,6 @@ export function _stringifyAny(obj: any, opt: StringifyAnyOptions = {}): string {
|
|
|
130
123
|
// `replace` here works ONCE, exactly as we need it
|
|
131
124
|
s = s.replace('HttpError', `HttpError(${obj.data.httpStatusCode})`)
|
|
132
125
|
}
|
|
133
|
-
|
|
134
|
-
// Here we ensure it has `data`
|
|
135
|
-
const { data } = obj
|
|
136
|
-
if (opt.includeErrorData && Object.keys(data).length > 0) {
|
|
137
|
-
s = [s, _stringifyAny(data, opt)].join('\n')
|
|
138
|
-
}
|
|
139
126
|
} else if (typeof (obj as any).code === 'string') {
|
|
140
127
|
// Error that has no `data`, but has `code` property
|
|
141
128
|
s = [s, `code: ${(obj as any).code}`].join('\n')
|
package/src/types.ts
CHANGED
|
@@ -80,6 +80,9 @@ export type UnsavedId<T extends Partial<ObjectWithId>> = Omit<T, 'id'> & {
|
|
|
80
80
|
*/
|
|
81
81
|
export type AnyFunction<T = any> = (...args: any[]) => T
|
|
82
82
|
export type AnyAsyncFunction<T = any> = (...args: any[]) => Promise<T>
|
|
83
|
+
export type AsyncFunction<T = any> = () => Promise<T>
|
|
84
|
+
export type AnyPromisableFunction<T = any> = (...args: any[]) => Promisable<T>
|
|
85
|
+
export type PromisableFunction<T = any> = () => Promisable<T>
|
|
83
86
|
|
|
84
87
|
/**
|
|
85
88
|
* Symbol to indicate END of Sequence.
|