@naturalcycles/js-lib 14.217.0 → 14.219.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.
@@ -115,8 +115,13 @@ export declare function _dropWhile<T>(items: T[], predicate: Predicate<T>): T[];
115
115
  export declare function _dropRightWhile<T>(items: T[], predicate: Predicate<T>): T[];
116
116
  /**
117
117
  * Counts how many items match the predicate.
118
+ *
119
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
120
+ */
121
+ export declare function _count<T>(items: T[], predicate: AbortablePredicate<T>, limit?: number): number;
122
+ /**
123
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
118
124
  */
119
- export declare function _count<T>(items: T[], predicate: AbortablePredicate<T>): number;
120
125
  export declare function _countBy<T>(items: T[], mapper: Mapper<T, any>): StringMap<number>;
121
126
  /**
122
127
  * Returns an intersection between 2 arrays.
@@ -206,19 +206,29 @@ function _dropRightWhile(items, predicate) {
206
206
  exports._dropRightWhile = _dropRightWhile;
207
207
  /**
208
208
  * Counts how many items match the predicate.
209
+ *
210
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
209
211
  */
210
- function _count(items, predicate) {
212
+ function _count(items, predicate, limit) {
213
+ if (limit === 0)
214
+ return 0;
211
215
  let count = 0;
212
216
  for (const [i, item] of items.entries()) {
213
217
  const r = predicate(item, i);
214
218
  if (r === types_1.END)
215
219
  break;
216
- if (r)
220
+ if (r) {
217
221
  count++;
222
+ if (limit && count >= limit)
223
+ break;
224
+ }
218
225
  }
219
226
  return count;
220
227
  }
221
228
  exports._count = _count;
229
+ /**
230
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
231
+ */
222
232
  function _countBy(items, mapper) {
223
233
  const map = {};
224
234
  items.forEach((item, i) => {
@@ -4,7 +4,11 @@ export interface PMapOptions {
4
4
  /**
5
5
  * Number of concurrently pending promises returned by `mapper`.
6
6
  *
7
- * @default Infinity
7
+ * Defaults to 16.
8
+ *
9
+ * It previously (and originally) defaulted to Infinity, which was later changed,
10
+ * because it's somewhat dangerous to run "infinite number of parallel promises".
11
+ * You can still emulate the old behavior by passing `Infinity`.
8
12
  */
9
13
  concurrency?: number;
10
14
  /**
@@ -23,6 +27,14 @@ export interface PMapOptions {
23
27
  logger?: CommonLogger | null;
24
28
  }
25
29
  /**
30
+ * Forked from https://github.com/sindresorhus/p-map
31
+ *
32
+ * Improvements:
33
+ * - Exported as { pMap }, so IDE auto-completion works
34
+ * - Included Typescript typings (no need for @types/p-map)
35
+ * - Compatible with pProps (that had typings issues)
36
+ * - Preserves async stack traces (in selected cases)
37
+ *
26
38
  * Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled,
27
39
  * or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned
28
40
  * from `mapper` in `input` order.
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
- /*
3
- Taken from https://github.com/sindresorhus/p-map
4
-
5
- Improvements:
6
- - Exported as { pMap }, so IDE auto-completion works
7
- - Included Typescript typings (no need for @types/p-map)
8
- - Compatible with pProps (that had typings issues)
9
- */
10
2
  Object.defineProperty(exports, "__esModule", { value: true });
11
3
  exports.pMap = void 0;
12
4
  const __1 = require("..");
13
5
  /**
6
+ * Forked from https://github.com/sindresorhus/p-map
7
+ *
8
+ * Improvements:
9
+ * - Exported as { pMap }, so IDE auto-completion works
10
+ * - Included Typescript typings (no need for @types/p-map)
11
+ * - Compatible with pProps (that had typings issues)
12
+ * - Preserves async stack traces (in selected cases)
13
+ *
14
14
  * Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled,
15
15
  * or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned
16
16
  * from `mapper` in `input` order.
@@ -37,70 +37,26 @@ const __1 = require("..");
37
37
  * })();
38
38
  */
39
39
  async function pMap(iterable, mapper, opt = {}) {
40
- const { logger = console } = opt;
41
- const ret = [];
42
- // const iterator = iterable[Symbol.iterator]()
43
40
  const items = [...iterable];
44
41
  const itemsLength = items.length;
45
42
  if (itemsLength === 0)
46
43
  return []; // short circuit
47
- const { concurrency = itemsLength, errorMode = __1.ErrorMode.THROW_IMMEDIATELY } = opt;
48
- const errors = [];
49
- let isSettled = false;
50
- let resolvingCount = 0;
51
- let currentIndex = 0;
44
+ const { concurrency = 16, errorMode = __1.ErrorMode.THROW_IMMEDIATELY, logger = console } = opt;
52
45
  // Special cases that are able to preserve async stack traces
46
+ // Special case: serial execution
53
47
  if (concurrency === 1) {
54
- // Special case for concurrency == 1
55
- for (const item of items) {
56
- try {
57
- const r = await mapper(item, currentIndex++);
58
- if (r === __1.END)
59
- break;
60
- if (r !== __1.SKIP)
61
- ret.push(r);
62
- }
63
- catch (err) {
64
- if (errorMode === __1.ErrorMode.THROW_IMMEDIATELY)
65
- throw err;
66
- if (errorMode === __1.ErrorMode.THROW_AGGREGATED) {
67
- errors.push(err);
68
- }
69
- else {
70
- // otherwise, suppress (but still log via logger)
71
- logger?.error(err);
72
- }
73
- }
74
- }
75
- if (errors.length) {
76
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
77
- }
78
- return ret;
48
+ return await pMap1(items, mapper, errorMode, logger);
79
49
  }
80
- else if (!opt.concurrency || items.length <= opt.concurrency) {
81
- // Special case for concurrency == infinity or iterable.length < concurrency
82
- if (errorMode === __1.ErrorMode.THROW_IMMEDIATELY) {
83
- return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(r => r !== __1.SKIP && r !== __1.END);
84
- }
85
- ;
86
- (await Promise.allSettled(items.map((item, i) => mapper(item, i)))).forEach(r => {
87
- if (r.status === 'fulfilled') {
88
- if (r.value !== __1.SKIP && r.value !== __1.END)
89
- ret.push(r.value);
90
- }
91
- else if (errorMode === __1.ErrorMode.THROW_AGGREGATED) {
92
- errors.push(r.reason);
93
- }
94
- else {
95
- // otherwise, suppress (but still log via logger)
96
- logger?.error(r.reason);
97
- }
98
- });
99
- if (errors.length) {
100
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
101
- }
102
- return ret;
50
+ // Special case: concurrency === Infinity or items.length <= concurrency
51
+ if (concurrency === Infinity || items.length <= concurrency) {
52
+ return await pMapAll(items, mapper, errorMode, logger);
103
53
  }
54
+ // General case: execution with throttled concurrency
55
+ const ret = [];
56
+ const errors = [];
57
+ let isSettled = false;
58
+ let resolvingCount = 0;
59
+ let currentIndex = 0;
104
60
  return await new Promise((resolve, reject) => {
105
61
  const next = () => {
106
62
  if (isSettled) {
@@ -159,3 +115,64 @@ async function pMap(iterable, mapper, opt = {}) {
159
115
  });
160
116
  }
161
117
  exports.pMap = pMap;
118
+ /**
119
+ pMap with serial (non-concurrent) execution.
120
+ */
121
+ async function pMap1(items, mapper, errorMode, logger) {
122
+ let i = 0;
123
+ const ret = [];
124
+ const errors = [];
125
+ for (const item of items) {
126
+ try {
127
+ const r = await mapper(item, i++);
128
+ if (r === __1.END)
129
+ break;
130
+ if (r !== __1.SKIP)
131
+ ret.push(r);
132
+ }
133
+ catch (err) {
134
+ if (errorMode === __1.ErrorMode.THROW_IMMEDIATELY)
135
+ throw err;
136
+ if (errorMode === __1.ErrorMode.THROW_AGGREGATED) {
137
+ errors.push(err);
138
+ }
139
+ else {
140
+ // otherwise, suppress (but still log via logger)
141
+ logger?.error(err);
142
+ }
143
+ }
144
+ }
145
+ if (errors.length) {
146
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
147
+ }
148
+ return ret;
149
+ }
150
+ /**
151
+ pMap with fully concurrent execution, like Promise.all
152
+ */
153
+ async function pMapAll(items, mapper, errorMode, logger) {
154
+ if (errorMode === __1.ErrorMode.THROW_IMMEDIATELY) {
155
+ return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(r => r !== __1.SKIP && r !== __1.END);
156
+ }
157
+ const ret = [];
158
+ const errors = [];
159
+ for (const r of await Promise.allSettled(items.map((item, i) => mapper(item, i)))) {
160
+ if (r.status === 'fulfilled') {
161
+ if (r.value === __1.END)
162
+ break;
163
+ if (r.value !== __1.SKIP)
164
+ ret.push(r.value);
165
+ }
166
+ else if (errorMode === __1.ErrorMode.THROW_AGGREGATED) {
167
+ errors.push(r.reason);
168
+ }
169
+ else {
170
+ // otherwise, suppress (but still log via logger)
171
+ logger?.error(r.reason);
172
+ }
173
+ }
174
+ if (errors.length) {
175
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
176
+ }
177
+ return ret;
178
+ }
@@ -2,5 +2,8 @@
2
2
  * Modified version of: https://github.com/sindresorhus/leven/
3
3
  *
4
4
  * Returns a Levenshtein distance between first and second word.
5
+ *
6
+ * `limit` optional parameter can be used to limit the distance calculation
7
+ * and skip unnecessary iterations when limit is reached.
5
8
  */
6
- export declare function _leven(first: string, second: string): number;
9
+ export declare function _leven(first: string, second: string, limit?: number): number;
@@ -8,9 +8,12 @@ const characterCodeCache = [];
8
8
  * Modified version of: https://github.com/sindresorhus/leven/
9
9
  *
10
10
  * Returns a Levenshtein distance between first and second word.
11
+ *
12
+ * `limit` optional parameter can be used to limit the distance calculation
13
+ * and skip unnecessary iterations when limit is reached.
11
14
  */
12
- function _leven(first, second) {
13
- if (first === second) {
15
+ function _leven(first, second, limit) {
16
+ if (first === second || limit === 0) {
14
17
  return 0;
15
18
  }
16
19
  const swap = first;
@@ -40,6 +43,8 @@ function _leven(first, second) {
40
43
  firstLength -= start;
41
44
  secondLength -= start;
42
45
  if (firstLength === 0) {
46
+ if (limit && secondLength >= limit)
47
+ return limit;
43
48
  return secondLength;
44
49
  }
45
50
  let bCharacterCode;
@@ -56,6 +61,8 @@ function _leven(first, second) {
56
61
  bCharacterCode = second.charCodeAt(start + index2);
57
62
  temporary = index2++;
58
63
  result = index2;
64
+ if (limit && result >= limit)
65
+ return limit; // exit early on limit
59
66
  for (index = 0; index < firstLength; index++) {
60
67
  temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
61
68
  temporary = array[index];
@@ -188,18 +188,28 @@ export function _dropRightWhile(items, predicate) {
188
188
  }
189
189
  /**
190
190
  * Counts how many items match the predicate.
191
+ *
192
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
191
193
  */
192
- export function _count(items, predicate) {
194
+ export function _count(items, predicate, limit) {
195
+ if (limit === 0)
196
+ return 0;
193
197
  let count = 0;
194
198
  for (const [i, item] of items.entries()) {
195
199
  const r = predicate(item, i);
196
200
  if (r === END)
197
201
  break;
198
- if (r)
202
+ if (r) {
199
203
  count++;
204
+ if (limit && count >= limit)
205
+ break;
206
+ }
200
207
  }
201
208
  return count;
202
209
  }
210
+ /**
211
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
212
+ */
203
213
  export function _countBy(items, mapper) {
204
214
  const map = {};
205
215
  items.forEach((item, i) => {
@@ -1,13 +1,13 @@
1
- /*
2
- Taken from https://github.com/sindresorhus/p-map
3
-
4
- Improvements:
5
- - Exported as { pMap }, so IDE auto-completion works
6
- - Included Typescript typings (no need for @types/p-map)
7
- - Compatible with pProps (that had typings issues)
8
- */
9
1
  import { END, ErrorMode, SKIP } from '..';
10
2
  /**
3
+ * Forked from https://github.com/sindresorhus/p-map
4
+ *
5
+ * Improvements:
6
+ * - Exported as { pMap }, so IDE auto-completion works
7
+ * - Included Typescript typings (no need for @types/p-map)
8
+ * - Compatible with pProps (that had typings issues)
9
+ * - Preserves async stack traces (in selected cases)
10
+ *
11
11
  * Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled,
12
12
  * or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned
13
13
  * from `mapper` in `input` order.
@@ -34,70 +34,26 @@ import { END, ErrorMode, SKIP } from '..';
34
34
  * })();
35
35
  */
36
36
  export async function pMap(iterable, mapper, opt = {}) {
37
- const { logger = console } = opt;
38
- const ret = [];
39
- // const iterator = iterable[Symbol.iterator]()
40
37
  const items = [...iterable];
41
38
  const itemsLength = items.length;
42
39
  if (itemsLength === 0)
43
40
  return []; // short circuit
44
- const { concurrency = itemsLength, errorMode = ErrorMode.THROW_IMMEDIATELY } = opt;
45
- const errors = [];
46
- let isSettled = false;
47
- let resolvingCount = 0;
48
- let currentIndex = 0;
41
+ const { concurrency = 16, errorMode = ErrorMode.THROW_IMMEDIATELY, logger = console } = opt;
49
42
  // Special cases that are able to preserve async stack traces
43
+ // Special case: serial execution
50
44
  if (concurrency === 1) {
51
- // Special case for concurrency == 1
52
- for (const item of items) {
53
- try {
54
- const r = await mapper(item, currentIndex++);
55
- if (r === END)
56
- break;
57
- if (r !== SKIP)
58
- ret.push(r);
59
- }
60
- catch (err) {
61
- if (errorMode === ErrorMode.THROW_IMMEDIATELY)
62
- throw err;
63
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
64
- errors.push(err);
65
- }
66
- else {
67
- // otherwise, suppress (but still log via logger)
68
- logger === null || logger === void 0 ? void 0 : logger.error(err);
69
- }
70
- }
71
- }
72
- if (errors.length) {
73
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
74
- }
75
- return ret;
45
+ return await pMap1(items, mapper, errorMode, logger);
76
46
  }
77
- else if (!opt.concurrency || items.length <= opt.concurrency) {
78
- // Special case for concurrency == infinity or iterable.length < concurrency
79
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
80
- return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(r => r !== SKIP && r !== END);
81
- }
82
- ;
83
- (await Promise.allSettled(items.map((item, i) => mapper(item, i)))).forEach(r => {
84
- if (r.status === 'fulfilled') {
85
- if (r.value !== SKIP && r.value !== END)
86
- ret.push(r.value);
87
- }
88
- else if (errorMode === ErrorMode.THROW_AGGREGATED) {
89
- errors.push(r.reason);
90
- }
91
- else {
92
- // otherwise, suppress (but still log via logger)
93
- logger === null || logger === void 0 ? void 0 : logger.error(r.reason);
94
- }
95
- });
96
- if (errors.length) {
97
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
98
- }
99
- return ret;
47
+ // Special case: concurrency === Infinity or items.length <= concurrency
48
+ if (concurrency === Infinity || items.length <= concurrency) {
49
+ return await pMapAll(items, mapper, errorMode, logger);
100
50
  }
51
+ // General case: execution with throttled concurrency
52
+ const ret = [];
53
+ const errors = [];
54
+ let isSettled = false;
55
+ let resolvingCount = 0;
56
+ let currentIndex = 0;
101
57
  return await new Promise((resolve, reject) => {
102
58
  const next = () => {
103
59
  if (isSettled) {
@@ -155,3 +111,64 @@ export async function pMap(iterable, mapper, opt = {}) {
155
111
  }
156
112
  });
157
113
  }
114
+ /**
115
+ pMap with serial (non-concurrent) execution.
116
+ */
117
+ async function pMap1(items, mapper, errorMode, logger) {
118
+ let i = 0;
119
+ const ret = [];
120
+ const errors = [];
121
+ for (const item of items) {
122
+ try {
123
+ const r = await mapper(item, i++);
124
+ if (r === END)
125
+ break;
126
+ if (r !== SKIP)
127
+ ret.push(r);
128
+ }
129
+ catch (err) {
130
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY)
131
+ throw err;
132
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
133
+ errors.push(err);
134
+ }
135
+ else {
136
+ // otherwise, suppress (but still log via logger)
137
+ logger === null || logger === void 0 ? void 0 : logger.error(err);
138
+ }
139
+ }
140
+ }
141
+ if (errors.length) {
142
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
143
+ }
144
+ return ret;
145
+ }
146
+ /**
147
+ pMap with fully concurrent execution, like Promise.all
148
+ */
149
+ async function pMapAll(items, mapper, errorMode, logger) {
150
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
151
+ return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(r => r !== SKIP && r !== END);
152
+ }
153
+ const ret = [];
154
+ const errors = [];
155
+ for (const r of await Promise.allSettled(items.map((item, i) => mapper(item, i)))) {
156
+ if (r.status === 'fulfilled') {
157
+ if (r.value === END)
158
+ break;
159
+ if (r.value !== SKIP)
160
+ ret.push(r.value);
161
+ }
162
+ else if (errorMode === ErrorMode.THROW_AGGREGATED) {
163
+ errors.push(r.reason);
164
+ }
165
+ else {
166
+ // otherwise, suppress (but still log via logger)
167
+ logger === null || logger === void 0 ? void 0 : logger.error(r.reason);
168
+ }
169
+ }
170
+ if (errors.length) {
171
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`);
172
+ }
173
+ return ret;
174
+ }
@@ -5,9 +5,12 @@ const characterCodeCache = [];
5
5
  * Modified version of: https://github.com/sindresorhus/leven/
6
6
  *
7
7
  * Returns a Levenshtein distance between first and second word.
8
+ *
9
+ * `limit` optional parameter can be used to limit the distance calculation
10
+ * and skip unnecessary iterations when limit is reached.
8
11
  */
9
- export function _leven(first, second) {
10
- if (first === second) {
12
+ export function _leven(first, second, limit) {
13
+ if (first === second || limit === 0) {
11
14
  return 0;
12
15
  }
13
16
  const swap = first;
@@ -37,6 +40,8 @@ export function _leven(first, second) {
37
40
  firstLength -= start;
38
41
  secondLength -= start;
39
42
  if (firstLength === 0) {
43
+ if (limit && secondLength >= limit)
44
+ return limit;
40
45
  return secondLength;
41
46
  }
42
47
  let bCharacterCode;
@@ -53,6 +58,8 @@ export function _leven(first, second) {
53
58
  bCharacterCode = second.charCodeAt(start + index2);
54
59
  temporary = index2++;
55
60
  result = index2;
61
+ if (limit && result >= limit)
62
+ return limit; // exit early on limit
56
63
  for (index = 0; index < firstLength; index++) {
57
64
  temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
58
65
  temporary = array[index];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.217.0",
3
+ "version": "14.219.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -229,19 +229,28 @@ export function _dropRightWhile<T>(items: T[], predicate: Predicate<T>): T[] {
229
229
 
230
230
  /**
231
231
  * Counts how many items match the predicate.
232
+ *
233
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
232
234
  */
233
- export function _count<T>(items: T[], predicate: AbortablePredicate<T>): number {
235
+ export function _count<T>(items: T[], predicate: AbortablePredicate<T>, limit?: number): number {
236
+ if (limit === 0) return 0
234
237
  let count = 0
235
238
 
236
239
  for (const [i, item] of items.entries()) {
237
240
  const r = predicate(item, i)
238
241
  if (r === END) break
239
- if (r) count++
242
+ if (r) {
243
+ count++
244
+ if (limit && count >= limit) break
245
+ }
240
246
  }
241
247
 
242
248
  return count
243
249
  }
244
250
 
251
+ /**
252
+ * `limit` allows to exit early when limit count is reached, skipping further iterations (perf optimization).
253
+ */
245
254
  export function _countBy<T>(items: T[], mapper: Mapper<T, any>): StringMap<number> {
246
255
  const map: StringMap<number> = {}
247
256
 
@@ -1,12 +1,3 @@
1
- /*
2
- Taken from https://github.com/sindresorhus/p-map
3
-
4
- Improvements:
5
- - Exported as { pMap }, so IDE auto-completion works
6
- - Included Typescript typings (no need for @types/p-map)
7
- - Compatible with pProps (that had typings issues)
8
- */
9
-
10
1
  import type { AbortableAsyncMapper, CommonLogger } from '..'
11
2
  import { END, ErrorMode, SKIP } from '..'
12
3
 
@@ -14,7 +5,11 @@ export interface PMapOptions {
14
5
  /**
15
6
  * Number of concurrently pending promises returned by `mapper`.
16
7
  *
17
- * @default Infinity
8
+ * Defaults to 16.
9
+ *
10
+ * It previously (and originally) defaulted to Infinity, which was later changed,
11
+ * because it's somewhat dangerous to run "infinite number of parallel promises".
12
+ * You can still emulate the old behavior by passing `Infinity`.
18
13
  */
19
14
  concurrency?: number
20
15
 
@@ -36,6 +31,14 @@ export interface PMapOptions {
36
31
  }
37
32
 
38
33
  /**
34
+ * Forked from https://github.com/sindresorhus/p-map
35
+ *
36
+ * Improvements:
37
+ * - Exported as { pMap }, so IDE auto-completion works
38
+ * - Included Typescript typings (no need for @types/p-map)
39
+ * - Compatible with pProps (that had typings issues)
40
+ * - Preserves async stack traces (in selected cases)
41
+ *
39
42
  * Returns a `Promise` that is fulfilled when all promises in `input` and ones returned from `mapper` are fulfilled,
40
43
  * or rejects if any of the promises reject. The fulfilled value is an `Array` of the fulfilled values returned
41
44
  * from `mapper` in `input` order.
@@ -66,73 +69,30 @@ export async function pMap<IN, OUT>(
66
69
  mapper: AbortableAsyncMapper<IN, OUT>,
67
70
  opt: PMapOptions = {},
68
71
  ): Promise<OUT[]> {
69
- const { logger = console } = opt
70
- const ret: (OUT | typeof SKIP)[] = []
71
- // const iterator = iterable[Symbol.iterator]()
72
72
  const items = [...iterable]
73
73
  const itemsLength = items.length
74
74
  if (itemsLength === 0) return [] // short circuit
75
75
 
76
- const { concurrency = itemsLength, errorMode = ErrorMode.THROW_IMMEDIATELY } = opt
77
-
78
- const errors: Error[] = []
79
- let isSettled = false
80
- let resolvingCount = 0
81
- let currentIndex = 0
76
+ const { concurrency = 16, errorMode = ErrorMode.THROW_IMMEDIATELY, logger = console } = opt
82
77
 
83
78
  // Special cases that are able to preserve async stack traces
84
-
79
+ // Special case: serial execution
85
80
  if (concurrency === 1) {
86
- // Special case for concurrency == 1
87
-
88
- for (const item of items) {
89
- try {
90
- const r = await mapper(item, currentIndex++)
91
- if (r === END) break
92
- if (r !== SKIP) ret.push(r)
93
- } catch (err) {
94
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) throw err
95
- if (errorMode === ErrorMode.THROW_AGGREGATED) {
96
- errors.push(err as Error)
97
- } else {
98
- // otherwise, suppress (but still log via logger)
99
- logger?.error(err)
100
- }
101
- }
102
- }
103
-
104
- if (errors.length) {
105
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`)
106
- }
107
-
108
- return ret as OUT[]
109
- } else if (!opt.concurrency || items.length <= opt.concurrency) {
110
- // Special case for concurrency == infinity or iterable.length < concurrency
111
-
112
- if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
113
- return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(
114
- r => r !== SKIP && r !== END,
115
- ) as OUT[]
116
- }
117
-
118
- ;(await Promise.allSettled(items.map((item, i) => mapper(item, i)))).forEach(r => {
119
- if (r.status === 'fulfilled') {
120
- if (r.value !== SKIP && r.value !== END) ret.push(r.value)
121
- } else if (errorMode === ErrorMode.THROW_AGGREGATED) {
122
- errors.push(r.reason)
123
- } else {
124
- // otherwise, suppress (but still log via logger)
125
- logger?.error(r.reason)
126
- }
127
- })
128
-
129
- if (errors.length) {
130
- throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`)
131
- }
81
+ return await pMap1(items, mapper, errorMode, logger)
82
+ }
132
83
 
133
- return ret as OUT[]
84
+ // Special case: concurrency === Infinity or items.length <= concurrency
85
+ if (concurrency === Infinity || items.length <= concurrency) {
86
+ return await pMapAll(items, mapper, errorMode, logger)
134
87
  }
135
88
 
89
+ // General case: execution with throttled concurrency
90
+ const ret: (OUT | typeof SKIP)[] = []
91
+ const errors: Error[] = []
92
+ let isSettled = false
93
+ let resolvingCount = 0
94
+ let currentIndex = 0
95
+
136
96
  return await new Promise<OUT[]>((resolve, reject) => {
137
97
  const next = (): void => {
138
98
  if (isSettled) {
@@ -198,3 +158,76 @@ export async function pMap<IN, OUT>(
198
158
  }
199
159
  })
200
160
  }
161
+
162
+ /**
163
+ pMap with serial (non-concurrent) execution.
164
+ */
165
+ async function pMap1<IN, OUT>(
166
+ items: IN[],
167
+ mapper: AbortableAsyncMapper<IN, OUT>,
168
+ errorMode: ErrorMode,
169
+ logger: CommonLogger | null,
170
+ ): Promise<OUT[]> {
171
+ let i = 0
172
+ const ret: OUT[] = []
173
+ const errors: Error[] = []
174
+
175
+ for (const item of items) {
176
+ try {
177
+ const r = await mapper(item, i++)
178
+ if (r === END) break
179
+ if (r !== SKIP) ret.push(r)
180
+ } catch (err) {
181
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) throw err
182
+ if (errorMode === ErrorMode.THROW_AGGREGATED) {
183
+ errors.push(err as Error)
184
+ } else {
185
+ // otherwise, suppress (but still log via logger)
186
+ logger?.error(err)
187
+ }
188
+ }
189
+ }
190
+
191
+ if (errors.length) {
192
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`)
193
+ }
194
+
195
+ return ret
196
+ }
197
+
198
+ /**
199
+ pMap with fully concurrent execution, like Promise.all
200
+ */
201
+ async function pMapAll<IN, OUT>(
202
+ items: IN[],
203
+ mapper: AbortableAsyncMapper<IN, OUT>,
204
+ errorMode: ErrorMode,
205
+ logger: CommonLogger | null,
206
+ ): Promise<OUT[]> {
207
+ if (errorMode === ErrorMode.THROW_IMMEDIATELY) {
208
+ return (await Promise.all(items.map((item, i) => mapper(item, i)))).filter(
209
+ r => r !== SKIP && r !== END,
210
+ ) as OUT[]
211
+ }
212
+
213
+ const ret: OUT[] = []
214
+ const errors: Error[] = []
215
+
216
+ for (const r of await Promise.allSettled(items.map((item, i) => mapper(item, i)))) {
217
+ if (r.status === 'fulfilled') {
218
+ if (r.value === END) break
219
+ if (r.value !== SKIP) ret.push(r.value)
220
+ } else if (errorMode === ErrorMode.THROW_AGGREGATED) {
221
+ errors.push(r.reason)
222
+ } else {
223
+ // otherwise, suppress (but still log via logger)
224
+ logger?.error(r.reason)
225
+ }
226
+ }
227
+
228
+ if (errors.length) {
229
+ throw new AggregateError(errors, `pMap resulted in ${errors.length} error(s)`)
230
+ }
231
+
232
+ return ret
233
+ }
@@ -7,9 +7,12 @@ const characterCodeCache: number[] = []
7
7
  * Modified version of: https://github.com/sindresorhus/leven/
8
8
  *
9
9
  * Returns a Levenshtein distance between first and second word.
10
+ *
11
+ * `limit` optional parameter can be used to limit the distance calculation
12
+ * and skip unnecessary iterations when limit is reached.
10
13
  */
11
- export function _leven(first: string, second: string): number {
12
- if (first === second) {
14
+ export function _leven(first: string, second: string, limit?: number): number {
15
+ if (first === second || limit === 0) {
13
16
  return 0
14
17
  }
15
18
 
@@ -47,6 +50,7 @@ export function _leven(first: string, second: string): number {
47
50
  secondLength -= start
48
51
 
49
52
  if (firstLength === 0) {
53
+ if (limit && secondLength >= limit) return limit
50
54
  return secondLength
51
55
  }
52
56
 
@@ -66,6 +70,7 @@ export function _leven(first: string, second: string): number {
66
70
  bCharacterCode = second.charCodeAt(start + index2)
67
71
  temporary = index2++
68
72
  result = index2
73
+ if (limit && result >= limit) return limit // exit early on limit
69
74
 
70
75
  for (index = 0; index < firstLength; index++) {
71
76
  temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1