@reliverse/mapkit 2.2.7
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/mod.d.ts +86 -0
- package/dist/mod.js +251 -0
- package/package.json +21 -0
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
type BaseOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Number of concurrently pending promises returned by `mapkit`.
|
|
4
|
+
*
|
|
5
|
+
* Must be an integer from 1 and up or `Infinity`.
|
|
6
|
+
*
|
|
7
|
+
* @default Infinity
|
|
8
|
+
*/
|
|
9
|
+
readonly concurrency?: number;
|
|
10
|
+
};
|
|
11
|
+
export type Options = BaseOptions & {
|
|
12
|
+
/**
|
|
13
|
+
* When `true`, the first mapkit rejection will be rejected back to the consumer.
|
|
14
|
+
*
|
|
15
|
+
* When `false`, instead of stopping when a promise rejects, it will wait for all
|
|
16
|
+
* the promises to settle and then reject with an `AggregateError` containing all
|
|
17
|
+
* the errors from the rejected promises.
|
|
18
|
+
*
|
|
19
|
+
* Caveat: When `true`, any already-started async mapkits will continue to run
|
|
20
|
+
* until they resolve or reject. In the case of infinite concurrency with sync
|
|
21
|
+
* iterables, all mapkits are invoked on startup and will continue after the first
|
|
22
|
+
* rejection. AbortControl can be used to stop pulling new items.
|
|
23
|
+
*
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
readonly stopOnError?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* You can abort the promises using `AbortController`.
|
|
29
|
+
*
|
|
30
|
+
* Rejects with the `signal.reason` immediately, stops pulling new items, and
|
|
31
|
+
* attempts to close the underlying iterator via `.return()` if available.
|
|
32
|
+
*/
|
|
33
|
+
readonly signal?: AbortSignal;
|
|
34
|
+
};
|
|
35
|
+
export type IterableOptions = BaseOptions & {
|
|
36
|
+
/**
|
|
37
|
+
* Maximum number of promises returned by `mapkit` that have resolved but not yet
|
|
38
|
+
* collected by the consumer of the async iterable. Calls to `mapkit` will be
|
|
39
|
+
* limited so that there is never too much backpressure.
|
|
40
|
+
*
|
|
41
|
+
* Useful whenever you are consuming the iterable slower than what the mapkit
|
|
42
|
+
* function can produce concurrently.
|
|
43
|
+
*
|
|
44
|
+
* Default: `options.concurrency`
|
|
45
|
+
*/
|
|
46
|
+
readonly backpressure?: number;
|
|
47
|
+
};
|
|
48
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
49
|
+
/**
|
|
50
|
+
* Function which is called for every item in `input`. Expected to return a
|
|
51
|
+
* `Promise` or value.
|
|
52
|
+
*
|
|
53
|
+
* @param element - Iterated element.
|
|
54
|
+
* @param index - Index of the element in the source array.
|
|
55
|
+
*/
|
|
56
|
+
export type mapkit<Element = unknown, NewElement = unknown> = (element: Element, index: number) => MaybePromise<NewElement | typeof pMapSkip>;
|
|
57
|
+
/**
|
|
58
|
+
* Return this value from a `mapkit` function to skip including the value in the
|
|
59
|
+
* returned array.
|
|
60
|
+
*/
|
|
61
|
+
export declare const pMapSkip: unique symbol;
|
|
62
|
+
/**
|
|
63
|
+
* @param input - Synchronous or asynchronous iterable that is iterated over
|
|
64
|
+
* concurrently, calling the `mapkit` function for each element. Each iterated
|
|
65
|
+
* item is `await`'d before the `mapkit` is invoked so the iterable may return a
|
|
66
|
+
* `Promise` that resolves to an item.
|
|
67
|
+
* @param mapkit - Function which is called for every item in `input`. Expected
|
|
68
|
+
* to return a `Promise` or value.
|
|
69
|
+
* @returns A `Promise` that is fulfilled when all promises in `input` and ones
|
|
70
|
+
* returned from `mapkit` are fulfilled, or rejects if any of the promises
|
|
71
|
+
* reject. The fulfilled value is an `Array` of the fulfilled values returned
|
|
72
|
+
* from `mapkit` in `input` order, excluding `pMapSkip`.
|
|
73
|
+
*/
|
|
74
|
+
export default function pMap<Element, NewElement>(input: AsyncIterable<Element | Promise<Element>> | Iterable<Element | Promise<Element>>, mapkit: mapkit<Element, NewElement>, options?: Options): Promise<Array<Exclude<NewElement, typeof pMapSkip>>>;
|
|
75
|
+
/**
|
|
76
|
+
* @param input - Synchronous or asynchronous iterable that is iterated over
|
|
77
|
+
* concurrently, calling the `mapkit` function for each element. Each iterated
|
|
78
|
+
* item is `await`'d before the `mapkit` is invoked so the iterable may return a
|
|
79
|
+
* `Promise` that resolves to an item.
|
|
80
|
+
* @param mapkit - Function which is called for every item in `input`. Expected
|
|
81
|
+
* to return a `Promise` or value.
|
|
82
|
+
* @returns An async iterable that streams each return value from `mapkit` in
|
|
83
|
+
* order, excluding `pMapSkip`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function pMapIterable<Element, NewElement>(input: AsyncIterable<Element | Promise<Element>> | Iterable<Element | Promise<Element>>, mapkit: mapkit<Element, NewElement>, options?: IterableOptions): AsyncIterable<Exclude<NewElement, typeof pMapSkip>>;
|
|
86
|
+
export {};
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export const pMapSkip = /* @__PURE__ */ Symbol("skip");
|
|
2
|
+
export default async function pMap(input, mapkit, options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
concurrency = Number.POSITIVE_INFINITY,
|
|
5
|
+
stopOnError = true,
|
|
6
|
+
signal
|
|
7
|
+
} = options;
|
|
8
|
+
if (!("Symbol" in globalThis) || input[Symbol.iterator] === void 0 && input[Symbol.asyncIterator] === void 0) {
|
|
9
|
+
throw new TypeError(
|
|
10
|
+
`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof input})`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
if (typeof mapkit !== "function") {
|
|
14
|
+
throw new TypeError("mapkit function is required");
|
|
15
|
+
}
|
|
16
|
+
if (!(Number.isSafeInteger(concurrency) && concurrency >= 1 || concurrency === Number.POSITIVE_INFINITY)) {
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const isAsyncIterable = input[Symbol.asyncIterator] !== void 0;
|
|
23
|
+
const iterator = isAsyncIterable ? input[Symbol.asyncIterator]() : input[Symbol.iterator]();
|
|
24
|
+
const results = [];
|
|
25
|
+
const errors = [];
|
|
26
|
+
let activeCount = 0;
|
|
27
|
+
let currentIndex = 0;
|
|
28
|
+
let isIterableDone = false;
|
|
29
|
+
let isSettled = false;
|
|
30
|
+
let isPumping = false;
|
|
31
|
+
let iteratorClosed = false;
|
|
32
|
+
const safeCloseIterator = async () => {
|
|
33
|
+
if (iteratorClosed) return;
|
|
34
|
+
const ret = iterator?.return;
|
|
35
|
+
if (typeof ret === "function") {
|
|
36
|
+
try {
|
|
37
|
+
iteratorClosed = true;
|
|
38
|
+
await ret.call(iterator);
|
|
39
|
+
} catch {
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
iteratorClosed = true;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const onAbort = () => {
|
|
46
|
+
if (isSettled) return;
|
|
47
|
+
isSettled = true;
|
|
48
|
+
void safeCloseIterator();
|
|
49
|
+
cleanup();
|
|
50
|
+
reject(signal?.reason);
|
|
51
|
+
};
|
|
52
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
53
|
+
function cleanup() {
|
|
54
|
+
signal?.removeEventListener("abort", onAbort);
|
|
55
|
+
}
|
|
56
|
+
function settleWithAggregateIfNeeded() {
|
|
57
|
+
if (errors.length > 0 && !stopOnError) {
|
|
58
|
+
cleanup();
|
|
59
|
+
reject(new AggregateError(errors, "One or more promises rejected"));
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function resolveResults() {
|
|
65
|
+
cleanup();
|
|
66
|
+
const out = [];
|
|
67
|
+
for (let i = 0; i < results.length; i++) {
|
|
68
|
+
const v = results[i];
|
|
69
|
+
if (v !== void 0 && v !== pMapSkip) {
|
|
70
|
+
out.push(v);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
resolve(out);
|
|
74
|
+
}
|
|
75
|
+
function checkCompletion() {
|
|
76
|
+
if (isSettled) return;
|
|
77
|
+
if (!isIterableDone) return;
|
|
78
|
+
if (activeCount > 0) return;
|
|
79
|
+
isSettled = true;
|
|
80
|
+
if (settleWithAggregateIfNeeded()) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
resolveResults();
|
|
84
|
+
}
|
|
85
|
+
async function startmapkit(valueOrPromise, index) {
|
|
86
|
+
try {
|
|
87
|
+
const item = await valueOrPromise;
|
|
88
|
+
const result = await mapkit(item, index);
|
|
89
|
+
if (!isSettled) {
|
|
90
|
+
results[index] = result;
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (isSettled) return;
|
|
94
|
+
if (stopOnError) {
|
|
95
|
+
isSettled = true;
|
|
96
|
+
void safeCloseIterator();
|
|
97
|
+
cleanup();
|
|
98
|
+
reject(error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
errors.push(error);
|
|
102
|
+
} finally {
|
|
103
|
+
if (!isSettled) {
|
|
104
|
+
activeCount--;
|
|
105
|
+
checkCompletion();
|
|
106
|
+
void pump();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function pump() {
|
|
111
|
+
if (isSettled || isPumping) return;
|
|
112
|
+
isPumping = true;
|
|
113
|
+
try {
|
|
114
|
+
while (!isSettled && !isIterableDone && activeCount < concurrency) {
|
|
115
|
+
let next;
|
|
116
|
+
try {
|
|
117
|
+
next = iterator.next();
|
|
118
|
+
next = await next;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (!isSettled) {
|
|
121
|
+
isSettled = true;
|
|
122
|
+
void safeCloseIterator();
|
|
123
|
+
cleanup();
|
|
124
|
+
reject(error);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (next.done) {
|
|
129
|
+
isIterableDone = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
const index = currentIndex++;
|
|
133
|
+
activeCount++;
|
|
134
|
+
void startmapkit(next.value, index);
|
|
135
|
+
}
|
|
136
|
+
checkCompletion();
|
|
137
|
+
} finally {
|
|
138
|
+
isPumping = false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
void pump();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
export function pMapIterable(input, mapkit, options = {}) {
|
|
145
|
+
const { concurrency = Number.POSITIVE_INFINITY, backpressure = concurrency } = options;
|
|
146
|
+
if (!("Symbol" in globalThis) || input[Symbol.iterator] === void 0 && input[Symbol.asyncIterator] === void 0) {
|
|
147
|
+
throw new TypeError(
|
|
148
|
+
`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof input})`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (typeof mapkit !== "function") {
|
|
152
|
+
throw new TypeError("mapkit function is required");
|
|
153
|
+
}
|
|
154
|
+
if (!(Number.isSafeInteger(concurrency) && concurrency >= 1 || concurrency === Number.POSITIVE_INFINITY)) {
|
|
155
|
+
throw new TypeError(
|
|
156
|
+
`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (!(Number.isSafeInteger(backpressure) && backpressure >= concurrency || backpressure === Number.POSITIVE_INFINITY)) {
|
|
160
|
+
throw new TypeError(
|
|
161
|
+
`Expected \`backpressure\` to be an integer from \`concurrency\` (${concurrency}) and up or \`Infinity\`, got \`${backpressure}\` (${typeof backpressure})`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
async *[Symbol.asyncIterator]() {
|
|
166
|
+
const iterator = input[Symbol.asyncIterator] !== void 0 ? input[Symbol.asyncIterator]() : input[Symbol.iterator]();
|
|
167
|
+
const resultQueue = [];
|
|
168
|
+
let runningCount = 0;
|
|
169
|
+
let currentIndex = 0;
|
|
170
|
+
let isDone = false;
|
|
171
|
+
let pulling = false;
|
|
172
|
+
let iteratorClosed = false;
|
|
173
|
+
const safeCloseIterator = async () => {
|
|
174
|
+
if (iteratorClosed) return;
|
|
175
|
+
const ret = iterator?.return;
|
|
176
|
+
if (typeof ret === "function") {
|
|
177
|
+
try {
|
|
178
|
+
iteratorClosed = true;
|
|
179
|
+
await ret.call(iterator);
|
|
180
|
+
} catch {
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
iteratorClosed = true;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const schedulePull = () => {
|
|
187
|
+
if (pulling || isDone) return;
|
|
188
|
+
pulling = true;
|
|
189
|
+
(async () => {
|
|
190
|
+
try {
|
|
191
|
+
while (!isDone && runningCount < concurrency && resultQueue.length < backpressure) {
|
|
192
|
+
let next;
|
|
193
|
+
try {
|
|
194
|
+
next = iterator.next();
|
|
195
|
+
next = await next;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
isDone = true;
|
|
198
|
+
resultQueue.push(
|
|
199
|
+
Promise.resolve({
|
|
200
|
+
error,
|
|
201
|
+
done: false
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
if (next.done) {
|
|
207
|
+
isDone = true;
|
|
208
|
+
resultQueue.push(Promise.resolve({ done: true }));
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
const index = currentIndex++;
|
|
212
|
+
runningCount++;
|
|
213
|
+
const promise = (async () => {
|
|
214
|
+
try {
|
|
215
|
+
const element = await next.value;
|
|
216
|
+
const result = await mapkit(element, index);
|
|
217
|
+
return { value: result, done: false };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return { error, done: false };
|
|
220
|
+
} finally {
|
|
221
|
+
runningCount--;
|
|
222
|
+
schedulePull();
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
resultQueue.push(promise);
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
pulling = false;
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
};
|
|
232
|
+
schedulePull();
|
|
233
|
+
while (resultQueue.length > 0) {
|
|
234
|
+
const result = await resultQueue.shift();
|
|
235
|
+
if (result.error) {
|
|
236
|
+
await safeCloseIterator();
|
|
237
|
+
throw result.error;
|
|
238
|
+
}
|
|
239
|
+
if (result.done) {
|
|
240
|
+
await safeCloseIterator();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
schedulePull();
|
|
244
|
+
if (result.value !== pMapSkip) {
|
|
245
|
+
yield result.value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await safeCloseIterator();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reliverse/mapkit",
|
|
3
|
+
"version": "2.2.7",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/mod.d.ts",
|
|
9
|
+
"default": "./dist/mod.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"package.json"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
}
|