@naturalcycles/js-lib 14.80.1 → 14.83.1
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/asyncMemo.decorator.d.ts +22 -0
- package/dist/decorators/asyncMemo.decorator.js +96 -0
- package/dist/decorators/memo.decorator.d.ts +8 -0
- package/dist/decorators/memo.decorator.js +11 -6
- package/dist/decorators/memo.util.d.ts +27 -8
- package/dist/decorators/memo.util.js +1 -1
- package/dist/decorators/retry.decorator.js +1 -1
- package/dist/error/error.model.d.ts +6 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -2
- package/dist/promise/AggregatedError.d.ts +1 -1
- package/dist/promise/AggregatedError.js +2 -7
- package/dist/promise/pFilter.d.ts +2 -3
- package/dist/promise/pFilter.js +4 -4
- package/dist/promise/pMap.d.ts +1 -1
- package/dist/promise/pMap.js +67 -19
- package/dist/promise/pRetry.d.ts +17 -2
- package/dist/promise/pRetry.js +72 -40
- package/dist-esm/decorators/asyncMemo.decorator.js +104 -0
- package/dist-esm/decorators/memo.decorator.js +11 -5
- package/dist-esm/decorators/memo.util.js +1 -1
- package/dist-esm/decorators/retry.decorator.js +2 -2
- package/dist-esm/index.js +3 -3
- package/dist-esm/promise/AggregatedError.js +2 -7
- package/dist-esm/promise/pFilter.js +4 -4
- package/dist-esm/promise/pMap.js +79 -19
- package/dist-esm/promise/pRetry.js +70 -39
- package/package.json +1 -1
- package/src/decorators/asyncMemo.decorator.ts +151 -0
- package/src/decorators/memo.decorator.ts +16 -5
- package/src/decorators/memo.util.ts +36 -10
- package/src/decorators/retry.decorator.ts +2 -2
- package/src/error/error.model.ts +7 -0
- package/src/index.ts +5 -3
- package/src/promise/AggregatedError.ts +3 -8
- package/src/promise/pFilter.ts +5 -14
- package/src/promise/pMap.ts +72 -21
- package/src/promise/pRetry.ts +105 -41
- package/dist/promise/pBatch.d.ts +0 -7
- package/dist/promise/pBatch.js +0 -30
- package/dist-esm/promise/pBatch.js +0 -23
- package/src/promise/pBatch.ts +0 -31
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;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { __asyncValues } from "tslib";
|
|
2
|
+
import { _since } from '../time/time.util';
|
|
3
|
+
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util';
|
|
4
|
+
import { CACHE_DROP } from './memo.decorator';
|
|
5
|
+
import { jsonMemoSerializer } from './memo.util';
|
|
6
|
+
/**
|
|
7
|
+
* Like @_Memo, but allowing async MemoCache implementation.
|
|
8
|
+
*
|
|
9
|
+
* Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
|
|
10
|
+
* Return `null` instead (it'll be cached).
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
13
|
+
export const _AsyncMemo = (opt) => (target, key, descriptor) => {
|
|
14
|
+
if (typeof descriptor.value !== 'function') {
|
|
15
|
+
throw new TypeError('Memoization can be applied only to methods');
|
|
16
|
+
}
|
|
17
|
+
const originalFn = descriptor.value;
|
|
18
|
+
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
19
|
+
const cache = new Map();
|
|
20
|
+
const { logHit = false, logMiss = false, noLogArgs = false, logger = console, cacheFactory, cacheKeyFn = jsonMemoSerializer, noCacheRejected = false, noCacheResolved = false, } = opt;
|
|
21
|
+
const keyStr = String(key);
|
|
22
|
+
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
23
|
+
descriptor.value = async function (...args) {
|
|
24
|
+
var e_1, _a;
|
|
25
|
+
const ctx = this;
|
|
26
|
+
const cacheKey = cacheKeyFn(args);
|
|
27
|
+
if (!cache.has(ctx)) {
|
|
28
|
+
cache.set(ctx, cacheFactory());
|
|
29
|
+
// here, no need to check the cache. It's definitely a miss, because the cacheLayers is just created
|
|
30
|
+
// UPD: no! AsyncMemo supports "persistent caches" (e.g Database-backed cache)
|
|
31
|
+
}
|
|
32
|
+
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
33
|
+
// Special event - CACHE_DROP
|
|
34
|
+
// Function will return undefined
|
|
35
|
+
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`);
|
|
36
|
+
try {
|
|
37
|
+
await Promise.all(cache.get(ctx).map(c => c.clear()));
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger.error(err);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let value;
|
|
45
|
+
try {
|
|
46
|
+
try {
|
|
47
|
+
for (var _b = __asyncValues(cache.get(ctx)), _c; _c = await _b.next(), !_c.done;) {
|
|
48
|
+
const cacheLayer = _c.value;
|
|
49
|
+
value = await cacheLayer.get(cacheKey);
|
|
50
|
+
if (value !== undefined) {
|
|
51
|
+
// it's a hit!
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
57
|
+
finally {
|
|
58
|
+
try {
|
|
59
|
+
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
|
|
60
|
+
}
|
|
61
|
+
finally { if (e_1) throw e_1.error; }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
// log error, but don't throw, treat it as a "miss"
|
|
66
|
+
logger.error(err);
|
|
67
|
+
}
|
|
68
|
+
if (value !== undefined) {
|
|
69
|
+
// hit!
|
|
70
|
+
if (logHit) {
|
|
71
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_AsyncMemo hit`);
|
|
72
|
+
}
|
|
73
|
+
return value instanceof Error ? Promise.reject(value) : Promise.resolve(value);
|
|
74
|
+
}
|
|
75
|
+
// Here we know it's a MISS, let's execute the real method
|
|
76
|
+
const started = Date.now();
|
|
77
|
+
try {
|
|
78
|
+
value = await originalFn.apply(ctx, args);
|
|
79
|
+
if (!noCacheResolved) {
|
|
80
|
+
Promise.all(cache.get(ctx).map(cacheLayer => cacheLayer.set(cacheKey, value))).catch(err => {
|
|
81
|
+
// log and ignore the error
|
|
82
|
+
logger.error(err);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (!noCacheRejected) {
|
|
89
|
+
// We put it to cache as raw Error, not Promise.reject(err)
|
|
90
|
+
Promise.all(cache.get(ctx).map(cacheLayer => cacheLayer.set(cacheKey, err))).catch(err => {
|
|
91
|
+
// log and ignore the error
|
|
92
|
+
logger.error(err);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
if (logMiss) {
|
|
99
|
+
logger.log(`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, noLogArgs)}) @_AsyncMemo miss (${_since(started)})`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
return descriptor;
|
|
104
|
+
};
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import { _since } from '../time/time.util';
|
|
7
7
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util';
|
|
8
8
|
import { jsonMemoSerializer, MapMemoCache } from './memo.util';
|
|
9
|
+
/**
|
|
10
|
+
* Symbol to indicate that the Cache should be dropped.
|
|
11
|
+
*/
|
|
12
|
+
export const CACHE_DROP = Symbol('CACHE_DROP');
|
|
9
13
|
/**
|
|
10
14
|
* Memoizes the method of the class, so it caches the output and returns the cached version if the "key"
|
|
11
15
|
* of the cache is the same. Key, by defaul, is calculated as `JSON.stringify(...args)`.
|
|
@@ -35,7 +39,14 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
35
39
|
const keyStr = String(key);
|
|
36
40
|
const methodSignature = _getTargetMethodSignature(target, keyStr);
|
|
37
41
|
descriptor.value = function (...args) {
|
|
42
|
+
var _a;
|
|
38
43
|
const ctx = this;
|
|
44
|
+
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
45
|
+
// Special event - CACHE_DROP
|
|
46
|
+
// Function will return undefined
|
|
47
|
+
logger.log(`${methodSignature} @_Memo.CACHE_DROP`);
|
|
48
|
+
return (_a = cache.get(ctx)) === null || _a === void 0 ? void 0 : _a.clear();
|
|
49
|
+
}
|
|
39
50
|
const cacheKey = cacheKeyFn(args);
|
|
40
51
|
if (!cache.has(ctx)) {
|
|
41
52
|
cache.set(ctx, cacheFactory());
|
|
@@ -88,10 +99,5 @@ export const _Memo = (opt = {}) => (target, key, descriptor) => {
|
|
|
88
99
|
return res;
|
|
89
100
|
}
|
|
90
101
|
};
|
|
91
|
-
descriptor.value.dropCache = () => {
|
|
92
|
-
logger.log(`${methodSignature} @_Memo.dropCache()`);
|
|
93
|
-
cache.forEach(memoCache => memoCache.clear());
|
|
94
|
-
cache.clear();
|
|
95
|
-
};
|
|
96
102
|
return descriptor;
|
|
97
103
|
};
|
|
@@ -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
|
@@ -8,6 +8,7 @@ export * from './decorators/debounce.decorator';
|
|
|
8
8
|
export * from './decorators/decorator.util';
|
|
9
9
|
export * from './decorators/logMethod.decorator';
|
|
10
10
|
export * from './decorators/memo.decorator';
|
|
11
|
+
export * from './decorators/asyncMemo.decorator';
|
|
11
12
|
export * from './decorators/memoFn';
|
|
12
13
|
export * from './decorators/retry.decorator';
|
|
13
14
|
export * from './decorators/timeout.decorator';
|
|
@@ -31,14 +32,13 @@ export * from './object/object.util';
|
|
|
31
32
|
export * from './object/sortObject';
|
|
32
33
|
export * from './object/sortObjectDeep';
|
|
33
34
|
import { AggregatedError } from './promise/AggregatedError';
|
|
34
|
-
export * from './promise/pBatch';
|
|
35
35
|
import { pDefer } from './promise/pDefer';
|
|
36
36
|
export * from './promise/pDelay';
|
|
37
37
|
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, };
|
|
@@ -5,17 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export class AggregatedError extends Error {
|
|
7
7
|
constructor(errors, results = []) {
|
|
8
|
-
const mappedErrors = errors.map(e => {
|
|
9
|
-
if (typeof e === 'string')
|
|
10
|
-
return new Error(e);
|
|
11
|
-
return e;
|
|
12
|
-
});
|
|
13
8
|
const message = [
|
|
14
9
|
`${errors.length} errors:`,
|
|
15
|
-
...
|
|
10
|
+
...errors.map((e, i) => `${i + 1}. ${e.message}`),
|
|
16
11
|
].join('\n');
|
|
17
12
|
super(message);
|
|
18
|
-
this.errors =
|
|
13
|
+
this.errors = errors;
|
|
19
14
|
this.results = results;
|
|
20
15
|
Object.defineProperty(this, 'name', {
|
|
21
16
|
value: this.constructor.name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
return
|
|
1
|
+
export async function pFilter(iterable, filterFn) {
|
|
2
|
+
const items = [...iterable];
|
|
3
|
+
const predicates = await Promise.all(items.map((item, i) => filterFn(item, i)));
|
|
4
|
+
return items.filter((item, i) => predicates[i]);
|
|
5
5
|
}
|
package/dist-esm/promise/pMap.js
CHANGED
|
@@ -6,6 +6,7 @@ Improvements:
|
|
|
6
6
|
- Included Typescript typings (no need for @types/p-map)
|
|
7
7
|
- Compatible with pProps (that had typings issues)
|
|
8
8
|
*/
|
|
9
|
+
import { __asyncValues } from "tslib";
|
|
9
10
|
import { END, ErrorMode, SKIP } from '..';
|
|
10
11
|
import { AggregatedError } from './AggregatedError';
|
|
11
12
|
/**
|
|
@@ -35,28 +36,85 @@ import { AggregatedError } from './AggregatedError';
|
|
|
35
36
|
* })();
|
|
36
37
|
*/
|
|
37
38
|
export async function pMap(iterable, mapper, opt = {}) {
|
|
39
|
+
var e_1, _a;
|
|
40
|
+
const ret = [];
|
|
41
|
+
// const iterator = iterable[Symbol.iterator]()
|
|
42
|
+
const items = [...iterable];
|
|
43
|
+
const itemsLength = items.length;
|
|
44
|
+
if (itemsLength === 0)
|
|
45
|
+
return []; // short circuit
|
|
46
|
+
const { concurrency = itemsLength, errorMode = ErrorMode.THROW_IMMEDIATELY } = opt;
|
|
47
|
+
const errors = [];
|
|
48
|
+
let isSettled = false;
|
|
49
|
+
let resolvingCount = 0;
|
|
50
|
+
let currentIndex = 0;
|
|
51
|
+
// Special cases that are able to preserve async stack traces
|
|
52
|
+
if (concurrency === 1) {
|
|
53
|
+
try {
|
|
54
|
+
// Special case for concurrency == 1
|
|
55
|
+
for (var items_1 = __asyncValues(items), items_1_1; items_1_1 = await items_1.next(), !items_1_1.done;) {
|
|
56
|
+
const item = items_1_1.value;
|
|
57
|
+
try {
|
|
58
|
+
const r = await mapper(item, currentIndex++);
|
|
59
|
+
if (r === END)
|
|
60
|
+
break;
|
|
61
|
+
if (r !== SKIP)
|
|
62
|
+
ret.push(r);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY)
|
|
66
|
+
throw err;
|
|
67
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
68
|
+
errors.push(err);
|
|
69
|
+
}
|
|
70
|
+
// otherwise, suppress completely
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
75
|
+
finally {
|
|
76
|
+
try {
|
|
77
|
+
if (items_1_1 && !items_1_1.done && (_a = items_1.return)) await _a.call(items_1);
|
|
78
|
+
}
|
|
79
|
+
finally { if (e_1) throw e_1.error; }
|
|
80
|
+
}
|
|
81
|
+
if (errors.length) {
|
|
82
|
+
throw new AggregatedError(errors, ret);
|
|
83
|
+
}
|
|
84
|
+
return ret;
|
|
85
|
+
}
|
|
86
|
+
else if (!opt.concurrency || items.length <= opt.concurrency) {
|
|
87
|
+
// Special case for concurrency == infinity or iterable.length < concurrency
|
|
88
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
|
|
89
|
+
return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(r => r !== SKIP && r !== END);
|
|
90
|
+
}
|
|
91
|
+
;
|
|
92
|
+
(await Promise.allSettled(items.map((item, i) => mapper(item, i)))).forEach(r => {
|
|
93
|
+
if (r.status === 'fulfilled') {
|
|
94
|
+
if (r.value !== SKIP && r.value !== END)
|
|
95
|
+
ret.push(r.value);
|
|
96
|
+
}
|
|
97
|
+
else if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
98
|
+
errors.push(r.reason);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (errors.length) {
|
|
102
|
+
throw new AggregatedError(errors, ret);
|
|
103
|
+
}
|
|
104
|
+
return ret;
|
|
105
|
+
}
|
|
38
106
|
return new Promise((resolve, reject) => {
|
|
39
|
-
const
|
|
40
|
-
const ret = [];
|
|
41
|
-
const iterator = iterable[Symbol.iterator]();
|
|
42
|
-
const errors = [];
|
|
43
|
-
let isSettled = false;
|
|
44
|
-
let isIterableDone = false;
|
|
45
|
-
let resolvingCount = 0;
|
|
46
|
-
let currentIndex = 0;
|
|
47
|
-
const next = (skipped = false) => {
|
|
107
|
+
const next = () => {
|
|
48
108
|
if (isSettled) {
|
|
49
109
|
return;
|
|
50
110
|
}
|
|
51
|
-
const nextItem =
|
|
52
|
-
const i = currentIndex
|
|
53
|
-
if (
|
|
54
|
-
currentIndex++;
|
|
55
|
-
if (nextItem.done) {
|
|
56
|
-
isIterableDone = true;
|
|
111
|
+
const nextItem = items[currentIndex];
|
|
112
|
+
const i = currentIndex++;
|
|
113
|
+
if (currentIndex > itemsLength) {
|
|
57
114
|
if (resolvingCount === 0) {
|
|
115
|
+
isSettled = true;
|
|
58
116
|
const r = ret.filter(r => r !== SKIP);
|
|
59
|
-
if (errors.length
|
|
117
|
+
if (errors.length) {
|
|
60
118
|
reject(new AggregatedError(errors, r));
|
|
61
119
|
}
|
|
62
120
|
else {
|
|
@@ -66,7 +124,7 @@ export async function pMap(iterable, mapper, opt = {}) {
|
|
|
66
124
|
return;
|
|
67
125
|
}
|
|
68
126
|
resolvingCount++;
|
|
69
|
-
Promise.resolve(nextItem
|
|
127
|
+
Promise.resolve(nextItem)
|
|
70
128
|
.then(async (element) => await mapper(element, i))
|
|
71
129
|
.then(value => {
|
|
72
130
|
if (value === END) {
|
|
@@ -82,7 +140,9 @@ export async function pMap(iterable, mapper, opt = {}) {
|
|
|
82
140
|
reject(err);
|
|
83
141
|
}
|
|
84
142
|
else {
|
|
85
|
-
|
|
143
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
144
|
+
errors.push(err);
|
|
145
|
+
}
|
|
86
146
|
resolvingCount--;
|
|
87
147
|
next();
|
|
88
148
|
}
|
|
@@ -90,7 +150,7 @@ export async function pMap(iterable, mapper, opt = {}) {
|
|
|
90
150
|
};
|
|
91
151
|
for (let i = 0; i < concurrency; i++) {
|
|
92
152
|
next();
|
|
93
|
-
if (
|
|
153
|
+
if (isSettled) {
|
|
94
154
|
break;
|
|
95
155
|
}
|
|
96
156
|
}
|
|
@@ -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
|
}
|