@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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { _since } from '../time/time.util'
|
|
2
|
+
import { Merge } from '../typeFest'
|
|
3
|
+
import { AnyObject } from '../types'
|
|
4
|
+
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
|
|
5
|
+
import { CACHE_DROP, MemoOptions } from './memo.decorator'
|
|
6
|
+
import { AsyncMemoCache, jsonMemoSerializer } from './memo.util'
|
|
7
|
+
|
|
8
|
+
export type AsyncMemoOptions = Merge<
|
|
9
|
+
MemoOptions,
|
|
10
|
+
{
|
|
11
|
+
/**
|
|
12
|
+
* Provide a custom implementation of MemoCache.
|
|
13
|
+
* Function that creates an instance of `MemoCache`.
|
|
14
|
+
* e.g LRUMemoCache from `@naturalcycles/nodejs-lib`.
|
|
15
|
+
*
|
|
16
|
+
* It's an ARRAY of Caches, to allow multiple layers of Cache.
|
|
17
|
+
* It will check it one by one, starting from the first.
|
|
18
|
+
* HIT will be returned immediately, MISS will go one level deeper, or returned (if the end of the Cache stack is reached).
|
|
19
|
+
*/
|
|
20
|
+
cacheFactory: () => AsyncMemoCache[]
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Like @_Memo, but allowing async MemoCache implementation.
|
|
26
|
+
*
|
|
27
|
+
* Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
|
|
28
|
+
* Return `null` instead (it'll be cached).
|
|
29
|
+
*/
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
31
|
+
export const _AsyncMemo =
|
|
32
|
+
(opt: AsyncMemoOptions): MethodDecorator =>
|
|
33
|
+
(target, key, descriptor) => {
|
|
34
|
+
if (typeof descriptor.value !== 'function') {
|
|
35
|
+
throw new TypeError('Memoization can be applied only to methods')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const originalFn = descriptor.value
|
|
39
|
+
|
|
40
|
+
// Map from "instance" of the Class where @_AsyncMemo is applied to AsyncMemoCache instance.
|
|
41
|
+
const cache = new Map<AnyObject, AsyncMemoCache[]>()
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
logHit = false,
|
|
45
|
+
logMiss = false,
|
|
46
|
+
noLogArgs = false,
|
|
47
|
+
logger = console,
|
|
48
|
+
cacheFactory,
|
|
49
|
+
cacheKeyFn = jsonMemoSerializer,
|
|
50
|
+
noCacheRejected = false,
|
|
51
|
+
noCacheResolved = false,
|
|
52
|
+
} = opt
|
|
53
|
+
|
|
54
|
+
const keyStr = String(key)
|
|
55
|
+
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
56
|
+
|
|
57
|
+
descriptor.value = async function (this: typeof target, ...args: any[]): Promise<any> {
|
|
58
|
+
const ctx = this
|
|
59
|
+
|
|
60
|
+
const cacheKey = cacheKeyFn(args)
|
|
61
|
+
|
|
62
|
+
if (!cache.has(ctx)) {
|
|
63
|
+
cache.set(ctx, cacheFactory())
|
|
64
|
+
// here, no need to check the cache. It's definitely a miss, because the cacheLayers is just created
|
|
65
|
+
// UPD: no! AsyncMemo supports "persistent caches" (e.g Database-backed cache)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
69
|
+
// Special event - CACHE_DROP
|
|
70
|
+
// Function will return undefined
|
|
71
|
+
logger.log(`${methodSignature} @_AsyncMemo.dropCache()`)
|
|
72
|
+
try {
|
|
73
|
+
await Promise.all(cache.get(ctx)!.map(c => c.clear()))
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.error(err)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let value: any
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
for await (const cacheLayer of cache.get(ctx)!) {
|
|
85
|
+
value = await cacheLayer.get(cacheKey)
|
|
86
|
+
if (value !== undefined) {
|
|
87
|
+
// it's a hit!
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// log error, but don't throw, treat it as a "miss"
|
|
93
|
+
logger.error(err)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (value !== undefined) {
|
|
97
|
+
// hit!
|
|
98
|
+
if (logHit) {
|
|
99
|
+
logger.log(
|
|
100
|
+
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
101
|
+
args,
|
|
102
|
+
noLogArgs,
|
|
103
|
+
)}) @_AsyncMemo hit`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return value instanceof Error ? Promise.reject(value) : Promise.resolve(value)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Here we know it's a MISS, let's execute the real method
|
|
111
|
+
const started = Date.now()
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
value = await originalFn.apply(ctx, args)
|
|
115
|
+
|
|
116
|
+
if (!noCacheResolved) {
|
|
117
|
+
Promise.all(cache.get(ctx)!.map(cacheLayer => cacheLayer.set(cacheKey, value))).catch(
|
|
118
|
+
err => {
|
|
119
|
+
// log and ignore the error
|
|
120
|
+
logger.error(err)
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return value
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!noCacheRejected) {
|
|
128
|
+
// We put it to cache as raw Error, not Promise.reject(err)
|
|
129
|
+
Promise.all(cache.get(ctx)!.map(cacheLayer => cacheLayer.set(cacheKey, err))).catch(
|
|
130
|
+
err => {
|
|
131
|
+
// log and ignore the error
|
|
132
|
+
logger.error(err)
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw err
|
|
138
|
+
} finally {
|
|
139
|
+
if (logMiss) {
|
|
140
|
+
logger.log(
|
|
141
|
+
`${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
|
|
142
|
+
args,
|
|
143
|
+
noLogArgs,
|
|
144
|
+
)}) @_AsyncMemo miss (${_since(started)})`,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} as any
|
|
149
|
+
|
|
150
|
+
return descriptor
|
|
151
|
+
}
|
|
@@ -10,6 +10,11 @@ import { AnyObject } from '../types'
|
|
|
10
10
|
import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
|
|
11
11
|
import { jsonMemoSerializer, MapMemoCache, MemoCache } from './memo.util'
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Symbol to indicate that the Cache should be dropped.
|
|
15
|
+
*/
|
|
16
|
+
export const CACHE_DROP = Symbol('CACHE_DROP')
|
|
17
|
+
|
|
13
18
|
export interface MemoOptions {
|
|
14
19
|
/**
|
|
15
20
|
* Default to false
|
|
@@ -45,12 +50,16 @@ export interface MemoOptions {
|
|
|
45
50
|
/**
|
|
46
51
|
* Don't cache resolved promises.
|
|
47
52
|
* Setting this to `true` will make the decorator to await the result.
|
|
53
|
+
*
|
|
54
|
+
* Default false.
|
|
48
55
|
*/
|
|
49
56
|
noCacheResolved?: boolean
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
59
|
* Don't cache rejected promises.
|
|
53
60
|
* Setting this to `true` will make the decorator to await the result.
|
|
61
|
+
*
|
|
62
|
+
* Default false.
|
|
54
63
|
*/
|
|
55
64
|
noCacheRejected?: boolean
|
|
56
65
|
}
|
|
@@ -102,6 +111,13 @@ export const _Memo =
|
|
|
102
111
|
descriptor.value = function (this: typeof target, ...args: any[]): any {
|
|
103
112
|
const ctx = this
|
|
104
113
|
|
|
114
|
+
if (args.length === 1 && args[0] === CACHE_DROP) {
|
|
115
|
+
// Special event - CACHE_DROP
|
|
116
|
+
// Function will return undefined
|
|
117
|
+
logger.log(`${methodSignature} @_Memo.CACHE_DROP`)
|
|
118
|
+
return cache.get(ctx)?.clear()
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
const cacheKey = cacheKeyFn(args)
|
|
106
122
|
|
|
107
123
|
if (!cache.has(ctx)) {
|
|
@@ -179,11 +195,6 @@ export const _Memo =
|
|
|
179
195
|
return res
|
|
180
196
|
}
|
|
181
197
|
} as any
|
|
182
|
-
;(descriptor.value as any).dropCache = () => {
|
|
183
|
-
logger.log(`${methodSignature} @_Memo.dropCache()`)
|
|
184
|
-
cache.forEach(memoCache => memoCache.clear())
|
|
185
|
-
cache.clear()
|
|
186
|
-
}
|
|
187
198
|
|
|
188
199
|
return descriptor
|
|
189
200
|
}
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { _isPrimitive } from '../object/object.util'
|
|
2
|
+
import { Promisable } from '../typeFest'
|
|
2
3
|
|
|
3
4
|
export type MemoSerializer = (args: any[]) => any
|
|
4
5
|
|
|
5
6
|
export const jsonMemoSerializer: MemoSerializer = args => {
|
|
6
|
-
if (
|
|
7
|
+
if (args.length === 0) return undefined
|
|
7
8
|
if (args.length === 1 && _isPrimitive(args[0])) return args[0]
|
|
8
9
|
return JSON.stringify(args)
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
export interface MemoCache {
|
|
12
|
-
has(k:
|
|
13
|
-
get(k:
|
|
14
|
-
set(k:
|
|
12
|
+
export interface MemoCache<KEY = any, VALUE = any> {
|
|
13
|
+
has(k: KEY): boolean
|
|
14
|
+
get(k: KEY): VALUE | undefined
|
|
15
|
+
set(k: KEY, v: VALUE): void
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Clear is only called when `.dropCache()` is called.
|
|
19
|
+
* Otherwise the Cache is "persistent" (never cleared).
|
|
20
|
+
*/
|
|
15
21
|
clear(): void
|
|
16
22
|
}
|
|
17
23
|
|
|
24
|
+
export interface AsyncMemoCache<KEY = any, VALUE = any> {
|
|
25
|
+
// `has` method is removed, because it is assumed that it has a cost and it's best to avoid doing both `has` and then `get`
|
|
26
|
+
// has(k: any): Promise<boolean>
|
|
27
|
+
/**
|
|
28
|
+
* `undefined` value returned indicates the ABSENCE of value in the Cache.
|
|
29
|
+
* This also means that you CANNOT store `undefined` value in the Cache, as it'll be treated as a MISS.
|
|
30
|
+
* You CAN store `null` value instead, it will be treated as a HIT.
|
|
31
|
+
*/
|
|
32
|
+
get(k: KEY): Promisable<VALUE | undefined>
|
|
33
|
+
set(k: KEY, v: VALUE): Promisable<void>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clear is only called when `.dropCache()` is called.
|
|
37
|
+
* Otherwise the Cache is "persistent" (never cleared).
|
|
38
|
+
*/
|
|
39
|
+
clear(): Promisable<void>
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
// SingleValueMemoCache and ObjectMemoCache are example-only, not used in production code
|
|
19
43
|
/*
|
|
20
44
|
export class SingleValueMemoCache implements MemoCache {
|
|
@@ -61,18 +85,20 @@ export class ObjectMemoCache implements MemoCache {
|
|
|
61
85
|
}
|
|
62
86
|
*/
|
|
63
87
|
|
|
64
|
-
export class MapMemoCache
|
|
65
|
-
|
|
88
|
+
export class MapMemoCache<KEY = any, VALUE = any>
|
|
89
|
+
implements MemoCache<KEY, VALUE>, AsyncMemoCache<KEY, VALUE>
|
|
90
|
+
{
|
|
91
|
+
private m = new Map<KEY, VALUE>()
|
|
66
92
|
|
|
67
|
-
has(k:
|
|
93
|
+
has(k: KEY): boolean {
|
|
68
94
|
return this.m.has(k)
|
|
69
95
|
}
|
|
70
96
|
|
|
71
|
-
get(k:
|
|
97
|
+
get(k: KEY): VALUE | undefined {
|
|
72
98
|
return this.m.get(k)
|
|
73
99
|
}
|
|
74
100
|
|
|
75
|
-
set(k:
|
|
101
|
+
set(k: KEY, v: VALUE): void {
|
|
76
102
|
this.m.set(k, v)
|
|
77
103
|
}
|
|
78
104
|
|
|
@@ -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/error/error.model.ts
CHANGED
|
@@ -31,6 +31,13 @@ export interface ErrorData {
|
|
|
31
31
|
*/
|
|
32
32
|
originalMessage?: string
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Can be used by error-reporting tools (e.g Sentry).
|
|
36
|
+
* If fingerprint is defined - it'll be used INSTEAD of default fingerprint of a tool.
|
|
37
|
+
* Can be used to force-group errors that are NOT needed to be split by endpoint or calling function.
|
|
38
|
+
*/
|
|
39
|
+
fingerprint?: string[]
|
|
40
|
+
|
|
34
41
|
/**
|
|
35
42
|
* Open-ended.
|
|
36
43
|
*/
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,8 @@ export * from './decorators/debounce.decorator'
|
|
|
12
12
|
export * from './decorators/decorator.util'
|
|
13
13
|
export * from './decorators/logMethod.decorator'
|
|
14
14
|
export * from './decorators/memo.decorator'
|
|
15
|
-
|
|
15
|
+
export * from './decorators/asyncMemo.decorator'
|
|
16
|
+
import { MemoCache, AsyncMemoCache } from './decorators/memo.util'
|
|
16
17
|
export * from './decorators/memoFn'
|
|
17
18
|
export * from './decorators/retry.decorator'
|
|
18
19
|
export * from './decorators/timeout.decorator'
|
|
@@ -67,14 +68,13 @@ export * from './object/object.util'
|
|
|
67
68
|
export * from './object/sortObject'
|
|
68
69
|
export * from './object/sortObjectDeep'
|
|
69
70
|
import { AggregatedError } from './promise/AggregatedError'
|
|
70
|
-
export * from './promise/pBatch'
|
|
71
71
|
import { DeferredPromise, pDefer } from './promise/pDefer'
|
|
72
72
|
export * from './promise/pDelay'
|
|
73
73
|
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'
|
|
@@ -161,6 +161,7 @@ export type {
|
|
|
161
161
|
AbortableAsyncMapper,
|
|
162
162
|
PQueueCfg,
|
|
163
163
|
MemoCache,
|
|
164
|
+
AsyncMemoCache,
|
|
164
165
|
PromiseDecoratorCfg,
|
|
165
166
|
PromiseDecoratorResp,
|
|
166
167
|
ErrorData,
|
|
@@ -250,6 +251,7 @@ export {
|
|
|
250
251
|
pDefer,
|
|
251
252
|
AggregatedError,
|
|
252
253
|
pRetry,
|
|
254
|
+
pRetryFn,
|
|
253
255
|
pTimeout,
|
|
254
256
|
pTimeoutFn,
|
|
255
257
|
_tryCatch,
|
|
@@ -7,20 +7,15 @@ export class AggregatedError<RESULT = any> extends Error {
|
|
|
7
7
|
errors!: Error[]
|
|
8
8
|
results!: RESULT[]
|
|
9
9
|
|
|
10
|
-
constructor(errors:
|
|
11
|
-
const mappedErrors = errors.map(e => {
|
|
12
|
-
if (typeof e === 'string') return new Error(e)
|
|
13
|
-
return e
|
|
14
|
-
})
|
|
15
|
-
|
|
10
|
+
constructor(errors: Error[], results: RESULT[] = []) {
|
|
16
11
|
const message = [
|
|
17
12
|
`${errors.length} errors:`,
|
|
18
|
-
...
|
|
13
|
+
...errors.map((e, i) => `${i + 1}. ${e.message}`),
|
|
19
14
|
].join('\n')
|
|
20
15
|
|
|
21
16
|
super(message)
|
|
22
17
|
|
|
23
|
-
this.errors =
|
|
18
|
+
this.errors = errors
|
|
24
19
|
this.results = results
|
|
25
20
|
|
|
26
21
|
Object.defineProperty(this, 'name', {
|
package/src/promise/pFilter.ts
CHANGED
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { pMap, PMapOptions } from './pMap'
|
|
1
|
+
import { AsyncPredicate } from '../types'
|
|
3
2
|
|
|
4
|
-
export async function pFilter<T>(
|
|
5
|
-
|
|
6
|
-
filterFn
|
|
7
|
-
|
|
8
|
-
): Promise<T[]> {
|
|
9
|
-
const values = await pMap(
|
|
10
|
-
iterable,
|
|
11
|
-
async (item, index) => await Promise.all([filterFn(item, index), item]),
|
|
12
|
-
opt,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
return values.filter(value => Boolean(value[0])).map(value => value[1])
|
|
3
|
+
export async function pFilter<T>(iterable: Iterable<T>, filterFn: AsyncPredicate<T>): Promise<T[]> {
|
|
4
|
+
const items = [...iterable]
|
|
5
|
+
const predicates = await Promise.all(items.map((item, i) => filterFn(item, i)))
|
|
6
|
+
return items.filter((item, i) => predicates[i])
|
|
16
7
|
}
|
package/src/promise/pMap.ts
CHANGED
|
@@ -55,36 +55,85 @@ export interface PMapOptions {
|
|
|
55
55
|
* })();
|
|
56
56
|
*/
|
|
57
57
|
export async function pMap<IN, OUT>(
|
|
58
|
-
iterable: Iterable<IN
|
|
58
|
+
iterable: Iterable<IN>,
|
|
59
59
|
mapper: AbortableAsyncMapper<IN, OUT>,
|
|
60
60
|
opt: PMapOptions = {},
|
|
61
61
|
): Promise<OUT[]> {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const ret: (OUT | typeof SKIP)[] = []
|
|
63
|
+
// const iterator = iterable[Symbol.iterator]()
|
|
64
|
+
const items = [...iterable]
|
|
65
|
+
const itemsLength = items.length
|
|
66
|
+
if (itemsLength === 0) return [] // short circuit
|
|
67
|
+
|
|
68
|
+
const { concurrency = itemsLength, errorMode = ErrorMode.THROW_IMMEDIATELY } = opt
|
|
69
|
+
|
|
70
|
+
const errors: Error[] = []
|
|
71
|
+
let isSettled = false
|
|
72
|
+
let resolvingCount = 0
|
|
73
|
+
let currentIndex = 0
|
|
74
|
+
|
|
75
|
+
// Special cases that are able to preserve async stack traces
|
|
76
|
+
|
|
77
|
+
if (concurrency === 1) {
|
|
78
|
+
// Special case for concurrency == 1
|
|
79
|
+
|
|
80
|
+
for await (const item of items) {
|
|
81
|
+
try {
|
|
82
|
+
const r = await mapper(item, currentIndex++)
|
|
83
|
+
if (r === END) break
|
|
84
|
+
if (r !== SKIP) ret.push(r)
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY) throw err
|
|
87
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
88
|
+
errors.push(err as Error)
|
|
89
|
+
}
|
|
90
|
+
// otherwise, suppress completely
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (errors.length) {
|
|
95
|
+
throw new AggregatedError(errors, ret)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return ret as OUT[]
|
|
99
|
+
} else if (!opt.concurrency || items.length <= opt.concurrency) {
|
|
100
|
+
// Special case for concurrency == infinity or iterable.length < concurrency
|
|
101
|
+
|
|
102
|
+
if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
|
|
103
|
+
return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(
|
|
104
|
+
r => r !== SKIP && r !== END,
|
|
105
|
+
) as OUT[]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
;(await Promise.allSettled(items.map((item, i) => mapper(item, i)))).forEach(r => {
|
|
109
|
+
if (r.status === 'fulfilled') {
|
|
110
|
+
if (r.value !== SKIP && r.value !== END) ret.push(r.value)
|
|
111
|
+
} else if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
112
|
+
errors.push(r.reason)
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (errors.length) {
|
|
117
|
+
throw new AggregatedError(errors, ret)
|
|
118
|
+
}
|
|
64
119
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const errors: Error[] = []
|
|
68
|
-
let isSettled = false
|
|
69
|
-
let isIterableDone = false
|
|
70
|
-
let resolvingCount = 0
|
|
71
|
-
let currentIndex = 0
|
|
120
|
+
return ret as OUT[]
|
|
121
|
+
}
|
|
72
122
|
|
|
73
|
-
|
|
123
|
+
return new Promise<OUT[]>((resolve, reject) => {
|
|
124
|
+
const next = () => {
|
|
74
125
|
if (isSettled) {
|
|
75
126
|
return
|
|
76
127
|
}
|
|
77
128
|
|
|
78
|
-
const nextItem =
|
|
79
|
-
const i = currentIndex
|
|
80
|
-
if (!skipped) currentIndex++
|
|
81
|
-
|
|
82
|
-
if (nextItem.done) {
|
|
83
|
-
isIterableDone = true
|
|
129
|
+
const nextItem = items[currentIndex]!
|
|
130
|
+
const i = currentIndex++
|
|
84
131
|
|
|
132
|
+
if (currentIndex > itemsLength) {
|
|
85
133
|
if (resolvingCount === 0) {
|
|
134
|
+
isSettled = true
|
|
86
135
|
const r = ret.filter(r => r !== SKIP) as OUT[]
|
|
87
|
-
if (errors.length
|
|
136
|
+
if (errors.length) {
|
|
88
137
|
reject(new AggregatedError(errors, r))
|
|
89
138
|
} else {
|
|
90
139
|
resolve(r)
|
|
@@ -96,7 +145,7 @@ export async function pMap<IN, OUT>(
|
|
|
96
145
|
|
|
97
146
|
resolvingCount++
|
|
98
147
|
|
|
99
|
-
Promise.resolve(nextItem
|
|
148
|
+
Promise.resolve(nextItem)
|
|
100
149
|
.then(async element => await mapper(element, i))
|
|
101
150
|
.then(
|
|
102
151
|
value => {
|
|
@@ -114,7 +163,9 @@ export async function pMap<IN, OUT>(
|
|
|
114
163
|
isSettled = true
|
|
115
164
|
reject(err)
|
|
116
165
|
} else {
|
|
117
|
-
|
|
166
|
+
if (errorMode === ErrorMode.THROW_AGGREGATED) {
|
|
167
|
+
errors.push(err)
|
|
168
|
+
}
|
|
118
169
|
resolvingCount--
|
|
119
170
|
next()
|
|
120
171
|
}
|
|
@@ -125,7 +176,7 @@ export async function pMap<IN, OUT>(
|
|
|
125
176
|
for (let i = 0; i < concurrency; i++) {
|
|
126
177
|
next()
|
|
127
178
|
|
|
128
|
-
if (
|
|
179
|
+
if (isSettled) {
|
|
129
180
|
break
|
|
130
181
|
}
|
|
131
182
|
}
|