@ix-xs/node-comfort 2.0.0 → 2.1.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/CHANGELOG.md +150 -0
- package/README.md +32 -30
- package/index.js +3 -3
- package/package.json +8 -7
- package/src/Arr.js +222 -58
- package/src/Async.js +322 -90
- package/src/Cache.js +148 -44
- package/src/Checker.js +257 -26
- package/src/Cli.js +404 -145
- package/src/Color.js +224 -83
- package/src/Crypto.js +481 -106
- package/src/Emitter.js +104 -37
- package/src/Env.js +176 -42
- package/src/FS.js +203 -60
- package/src/Func.js +226 -76
- package/src/Http.js +311 -149
- package/src/Id.js +281 -96
- package/src/Logger.js +302 -78
- package/src/Num.js +182 -60
- package/src/Obj.js +340 -125
- package/src/SQLite.js +426 -105
- package/src/Schema.js +602 -87
- package/src/Str.js +316 -86
- package/src/Sys.js +197 -54
- package/src/Time.js +572 -317
- package/src/Utils.js +62 -41
- package/src/errors.js +54 -11
- package/src/internal/shared.js +415 -0
- package/types/index.d.ts +59 -17
- package/types/src/Arr.d.ts +78 -0
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +52 -1
- package/types/src/Async.d.ts.map +1 -1
- package/types/src/Cache.d.ts +33 -7
- package/types/src/Cache.d.ts.map +1 -1
- package/types/src/Checker.d.ts +155 -2
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +97 -15
- package/types/src/Cli.d.ts.map +1 -1
- package/types/src/Color.d.ts +7 -1
- package/types/src/Color.d.ts.map +1 -1
- package/types/src/Crypto.d.ts +99 -13
- package/types/src/Crypto.d.ts.map +1 -1
- package/types/src/Emitter.d.ts +36 -17
- package/types/src/Emitter.d.ts.map +1 -1
- package/types/src/Env.d.ts +31 -1
- package/types/src/Env.d.ts.map +1 -1
- package/types/src/FS.d.ts +36 -5
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +147 -19
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +113 -43
- package/types/src/Http.d.ts.map +1 -1
- package/types/src/Id.d.ts +60 -8
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +53 -2
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +48 -7
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +67 -4
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +123 -14
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +256 -7
- package/types/src/Schema.d.ts.map +1 -1
- package/types/src/Str.d.ts +49 -1
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +51 -6
- package/types/src/Sys.d.ts.map +1 -1
- package/types/src/Time.d.ts +130 -34
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +15 -11
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +27 -3
- package/types/src/errors.d.ts.map +1 -1
- package/types/src/internal/shared.d.ts +123 -0
- package/types/src/internal/shared.d.ts.map +1 -0
package/src/Async.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { AbortError, TimeoutError } = require("./errors.js");
|
|
17
|
+
const { toMs } = require("./internal/shared.js");
|
|
17
18
|
const { retry, timeout } = require("./Func.js");
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -24,6 +25,12 @@ const { retry, timeout } = require("./Func.js");
|
|
|
24
25
|
* @property {AbortSignal} [signal] Stops starting new work and rejects with an `AbortError`.
|
|
25
26
|
*/
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* A rate limiter from `rateLimit()`. Call it with a function to run it as
|
|
30
|
+
* soon as the rate allows.
|
|
31
|
+
* @typedef {(<T>(fn: () => T | PromiseLike<T>) => Promise<Awaited<T>>) & { readonly pendingCount: number }} RateLimiter
|
|
32
|
+
*/
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* A limiter from `limit()`. Call it with a function to run it when there's room.
|
|
29
36
|
* @typedef {(<T>(fn: () => T | PromiseLike<T>) => Promise<Awaited<T>>) & { readonly activeCount: number, readonly pendingCount: number, concurrency: number, clearQueue(): void, onIdle(): Promise<void> }} Limiter
|
|
@@ -95,13 +102,104 @@ const { retry, timeout } = require("./Func.js");
|
|
|
95
102
|
*/
|
|
96
103
|
|
|
97
104
|
/** @param {number | string | undefined} value */
|
|
98
|
-
const _ms = (value) =>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
const _ms = (value) => (value === undefined ? undefined : toMs(value));
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A first-in first-out list where taking the first item costs nothing,
|
|
109
|
+
* unlike `Array.prototype.shift` on a long array.
|
|
110
|
+
* @template T
|
|
111
|
+
*/
|
|
112
|
+
class _Fifo {
|
|
113
|
+
/** @type {T[]} */
|
|
114
|
+
#items = [];
|
|
115
|
+
#head = 0;
|
|
116
|
+
|
|
117
|
+
/** @param {T} item */
|
|
118
|
+
push(item) {
|
|
119
|
+
this.#items.push(item);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @returns {T | undefined} */
|
|
123
|
+
shift() {
|
|
124
|
+
if (this.#head >= this.#items.length) return undefined;
|
|
125
|
+
const item = this.#items[this.#head];
|
|
126
|
+
this.#items[this.#head++] = /** @type {any} */ (undefined);
|
|
127
|
+
if (this.#head > 1024 && this.#head * 2 > this.#items.length) {
|
|
128
|
+
this.#items = this.#items.slice(this.#head);
|
|
129
|
+
this.#head = 0;
|
|
130
|
+
}
|
|
131
|
+
return item;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
clear() {
|
|
135
|
+
this.#items = [];
|
|
136
|
+
this.#head = 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get size() {
|
|
140
|
+
return this.#items.length - this.#head;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* A binary heap: the entry with the highest priority comes out first, and
|
|
146
|
+
* entries with the same priority come out in the order they went in.
|
|
147
|
+
* @template {{ priority: number, order: number }} T
|
|
148
|
+
*/
|
|
149
|
+
class _Heap {
|
|
150
|
+
/** @type {T[]} */
|
|
151
|
+
#items = [];
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {T} a
|
|
155
|
+
* @param {T} b
|
|
156
|
+
*/
|
|
157
|
+
#before(a, b) {
|
|
158
|
+
return a.priority > b.priority || (a.priority === b.priority && a.order < b.order);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @param {T} item */
|
|
162
|
+
push(item) {
|
|
163
|
+
const items = this.#items;
|
|
164
|
+
let i = items.length;
|
|
165
|
+
items.push(item);
|
|
166
|
+
while (i > 0) {
|
|
167
|
+
const parent = (i - 1) >> 1;
|
|
168
|
+
if (!this.#before(item, items[parent])) break;
|
|
169
|
+
items[i] = items[parent];
|
|
170
|
+
i = parent;
|
|
171
|
+
}
|
|
172
|
+
items[i] = item;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @returns {T | undefined} */
|
|
176
|
+
pop() {
|
|
177
|
+
const items = this.#items;
|
|
178
|
+
const top = items[0];
|
|
179
|
+
const last = items.pop();
|
|
180
|
+
if (items.length && last !== undefined) {
|
|
181
|
+
let i = 0;
|
|
182
|
+
const half = items.length >> 1;
|
|
183
|
+
while (i < half) {
|
|
184
|
+
let child = i * 2 + 1;
|
|
185
|
+
if (child + 1 < items.length && this.#before(items[child + 1], items[child])) child++;
|
|
186
|
+
if (!this.#before(items[child], last)) break;
|
|
187
|
+
items[i] = items[child];
|
|
188
|
+
i = child;
|
|
189
|
+
}
|
|
190
|
+
items[i] = last;
|
|
191
|
+
}
|
|
192
|
+
return top;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
clear() {
|
|
196
|
+
this.#items = [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
get size() {
|
|
200
|
+
return this.#items.length;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
105
203
|
|
|
106
204
|
/**
|
|
107
205
|
* Waits for a while. Same as `nc.wait()`.
|
|
@@ -119,10 +217,12 @@ function sleep(duration, options = {}) {
|
|
|
119
217
|
|
|
120
218
|
/**
|
|
121
219
|
* Maps items through an async function, with at most `concurrency` calls
|
|
122
|
-
* at once. Results stay in order.
|
|
220
|
+
* at once. Results stay in order. Items are taken from the source as slots
|
|
221
|
+
* free up, so a stream is never read faster than it's processed.
|
|
123
222
|
*
|
|
124
223
|
* @example
|
|
125
224
|
* const users = await async.map(ids, (id) => api.getUser(id), { concurrency: 10 });
|
|
225
|
+
* await async.forEach(readline.createInterface({ input }), importLine, { concurrency: 20 });
|
|
126
226
|
*
|
|
127
227
|
* @template T
|
|
128
228
|
* @template R
|
|
@@ -132,67 +232,108 @@ function sleep(duration, options = {}) {
|
|
|
132
232
|
* @returns {Promise<Array<Awaited<R>>>}
|
|
133
233
|
* @throws The first error, an `AggregateError` with `stopOnError: false`, or an `AbortError`.
|
|
134
234
|
*/
|
|
135
|
-
|
|
136
|
-
/** @type {T[]} */
|
|
137
|
-
const list = [];
|
|
138
|
-
if (Symbol.asyncIterator in Object(items)) for await (const item of /** @type {AsyncIterable<T>} */ (items)) list.push(item);
|
|
139
|
-
else for (const item of /** @type {Iterable<T>} */ (items)) list.push(item);
|
|
235
|
+
function map(items, fn, options = {}) {
|
|
140
236
|
const concurrency = Math.max(1, options.concurrency ?? Infinity);
|
|
141
237
|
const stopOnError = options.stopOnError ?? true;
|
|
238
|
+
const signal = options.signal;
|
|
142
239
|
/** @type {Array<Awaited<R>>} */
|
|
143
|
-
const results =
|
|
240
|
+
const results = [];
|
|
144
241
|
/** @type {unknown[]} */
|
|
145
242
|
const errors = [];
|
|
146
|
-
let next = 0;
|
|
147
|
-
let failed = false;
|
|
148
243
|
|
|
149
|
-
|
|
150
|
-
|
|
244
|
+
return new Promise((resolve, reject) => {
|
|
245
|
+
const source = /** @type {any} */ (items);
|
|
246
|
+
const isAsync = source !== null && source !== undefined && typeof source[Symbol.asyncIterator] === "function";
|
|
247
|
+
/** @type {Iterator<T> | AsyncIterator<T>} */
|
|
248
|
+
let iterator;
|
|
249
|
+
try {
|
|
250
|
+
iterator = isAsync ? source[Symbol.asyncIterator]() : source[Symbol.iterator]();
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return reject(error);
|
|
253
|
+
}
|
|
254
|
+
let started = 0;
|
|
151
255
|
let active = 0;
|
|
152
|
-
let
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
256
|
+
let exhausted = false;
|
|
257
|
+
let pulling = false;
|
|
258
|
+
let settled = false;
|
|
259
|
+
|
|
260
|
+
/** @param {unknown} [error] */
|
|
261
|
+
const end = (error) => {
|
|
262
|
+
if (settled) return;
|
|
263
|
+
settled = true;
|
|
264
|
+
signal?.removeEventListener("abort", onAbort);
|
|
265
|
+
// Stopped early: let the source release what it holds (an open file, a cursor).
|
|
266
|
+
if (!exhausted) void Promise.resolve().then(() => iterator.return?.()).catch(() => {});
|
|
267
|
+
if (error !== undefined) return reject(error);
|
|
268
|
+
results.length = started;
|
|
269
|
+
const failures = errors.filter((e) => e !== undefined);
|
|
270
|
+
if (failures.length) reject(new AggregateError(failures, `${failures.length} of ${started} tasks failed`));
|
|
271
|
+
else resolve(results);
|
|
156
272
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
273
|
+
const onAbort = () => end(new AbortError(undefined, { cause: signal?.reason }));
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {T} item
|
|
277
|
+
* @param {number} index
|
|
278
|
+
*/
|
|
279
|
+
const run = (item, index) => {
|
|
280
|
+
active++;
|
|
281
|
+
/** @type {Promise<any>} */
|
|
282
|
+
let work;
|
|
283
|
+
try {
|
|
284
|
+
work = Promise.resolve(fn(item, index));
|
|
285
|
+
} catch (error) {
|
|
286
|
+
work = Promise.reject(error);
|
|
287
|
+
}
|
|
288
|
+
work.then(
|
|
289
|
+
(value) => {
|
|
290
|
+
results[index] = value;
|
|
291
|
+
},
|
|
292
|
+
(error) => {
|
|
293
|
+
if (stopOnError) end(error ?? new Error("Task failed"));
|
|
294
|
+
else errors[index] = error ?? new Error("Task failed");
|
|
295
|
+
},
|
|
296
|
+
).then(() => {
|
|
297
|
+
active--;
|
|
298
|
+
pump();
|
|
299
|
+
});
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const pump = () => {
|
|
303
|
+
if (settled || pulling) return;
|
|
304
|
+
while (!exhausted && active < concurrency) {
|
|
305
|
+
/** @type {any} */
|
|
306
|
+
let step;
|
|
307
|
+
try {
|
|
308
|
+
step = iterator.next();
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return end(error ?? new Error("Iterator failed"));
|
|
311
|
+
}
|
|
312
|
+
if (isAsync) {
|
|
313
|
+
pulling = true;
|
|
314
|
+
/** @type {Promise<IteratorResult<T>>} */ (step).then(
|
|
315
|
+
(result) => {
|
|
316
|
+
pulling = false;
|
|
317
|
+
if (settled) return;
|
|
318
|
+
if (result.done) exhausted = true;
|
|
319
|
+
else run(result.value, started++);
|
|
320
|
+
pump();
|
|
177
321
|
},
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (errors.length) reject(new AggregateError(errors.filter((e) => e !== undefined), `${errors.filter((e) => e !== undefined).length} of ${list.length} tasks failed`));
|
|
186
|
-
else resolve(undefined);
|
|
187
|
-
} else {
|
|
188
|
-
launch();
|
|
189
|
-
}
|
|
190
|
-
});
|
|
322
|
+
(error) => end(error ?? new Error("Iterator failed")),
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (step.done) exhausted = true;
|
|
327
|
+
else run(step.value, started++);
|
|
328
|
+
if (settled) return;
|
|
191
329
|
}
|
|
330
|
+
if (exhausted && active === 0) end();
|
|
192
331
|
};
|
|
193
|
-
|
|
332
|
+
|
|
333
|
+
if (signal?.aborted) return onAbort();
|
|
334
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
335
|
+
pump();
|
|
194
336
|
});
|
|
195
|
-
return results;
|
|
196
337
|
}
|
|
197
338
|
|
|
198
339
|
/**
|
|
@@ -227,9 +368,10 @@ async function forEach(items, fn, options = {}) {
|
|
|
227
368
|
async function filter(items, predicate, options = {}) {
|
|
228
369
|
/** @type {T[]} */
|
|
229
370
|
const list = [];
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
371
|
+
const keep = await map(items, (item, index) => {
|
|
372
|
+
list[index] = item;
|
|
373
|
+
return predicate(item, index);
|
|
374
|
+
}, options);
|
|
233
375
|
return list.filter((_, i) => keep[i]);
|
|
234
376
|
}
|
|
235
377
|
|
|
@@ -250,6 +392,23 @@ async function series(tasks) {
|
|
|
250
392
|
return out;
|
|
251
393
|
}
|
|
252
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Runs async functions at the same time, or a few at once with
|
|
397
|
+
* `concurrency`, and returns their results in order.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* const [users, posts] = await async.parallel([() => loadUsers(), () => loadPosts()]);
|
|
401
|
+
* await async.parallel(jobs, { concurrency: 4 });
|
|
402
|
+
*
|
|
403
|
+
* @template T
|
|
404
|
+
* @param {Iterable<() => T | PromiseLike<T>>} tasks
|
|
405
|
+
* @param {MapOptions} [options]
|
|
406
|
+
* @returns {Promise<Array<Awaited<T>>>}
|
|
407
|
+
*/
|
|
408
|
+
function parallel(tasks, options = {}) {
|
|
409
|
+
return map(tasks, (task) => task(), options);
|
|
410
|
+
}
|
|
411
|
+
|
|
253
412
|
/**
|
|
254
413
|
* A limiter: wrap calls with it, and at most `concurrency` run at once
|
|
255
414
|
* while the rest wait their turn. Like `p-limit`.
|
|
@@ -264,17 +423,19 @@ async function series(tasks) {
|
|
|
264
423
|
function limit(concurrency) {
|
|
265
424
|
let max = Math.max(1, concurrency);
|
|
266
425
|
let active = 0;
|
|
267
|
-
/** @type {
|
|
268
|
-
const waiting =
|
|
426
|
+
/** @type {_Fifo<() => void>} */
|
|
427
|
+
const waiting = new _Fifo();
|
|
269
428
|
/** @type {Array<() => void>} */
|
|
270
429
|
let idleWaiters = [];
|
|
271
430
|
const next = () => {
|
|
272
|
-
|
|
431
|
+
while (active < max && waiting.size) {
|
|
273
432
|
active++;
|
|
274
433
|
/** @type {() => void} */ (waiting.shift())();
|
|
275
|
-
}
|
|
276
|
-
|
|
434
|
+
}
|
|
435
|
+
if (active === 0 && !waiting.size && idleWaiters.length) {
|
|
436
|
+
const resolvers = idleWaiters;
|
|
277
437
|
idleWaiters = [];
|
|
438
|
+
for (const resolve of resolvers) resolve();
|
|
278
439
|
}
|
|
279
440
|
};
|
|
280
441
|
/** @type {any} */
|
|
@@ -293,20 +454,65 @@ function limit(concurrency) {
|
|
|
293
454
|
});
|
|
294
455
|
Object.defineProperties(limiter, {
|
|
295
456
|
activeCount: { get: () => active },
|
|
296
|
-
pendingCount: { get: () => waiting.
|
|
457
|
+
pendingCount: { get: () => waiting.size },
|
|
297
458
|
concurrency: {
|
|
298
459
|
get: () => max,
|
|
299
460
|
set: (/** @type {number} */ value) => {
|
|
300
461
|
max = Math.max(1, value);
|
|
301
|
-
|
|
462
|
+
next();
|
|
302
463
|
},
|
|
303
464
|
},
|
|
304
|
-
clearQueue: { value: () =>
|
|
305
|
-
onIdle: { value: () => (active === 0 && !waiting.
|
|
465
|
+
clearQueue: { value: () => waiting.clear() },
|
|
466
|
+
onIdle: { value: () => (active === 0 && !waiting.size ? Promise.resolve() : new Promise((resolve) => idleWaiters.push(() => resolve(undefined)))) },
|
|
306
467
|
});
|
|
307
468
|
return limiter;
|
|
308
469
|
}
|
|
309
470
|
|
|
471
|
+
/**
|
|
472
|
+
* A rate limiter: at most `max` calls start within any `interval`. Made
|
|
473
|
+
* for APIs that allow, say, 10 requests per second.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* const throttled = async.rateLimit(10, "1s");
|
|
477
|
+
* const pages = await Promise.all(ids.map((id) => throttled(() => api.get(`items/${id}`))));
|
|
478
|
+
*
|
|
479
|
+
* @param {number} max How many calls may start per interval.
|
|
480
|
+
* @param {number | string} interval The window, in ms or like `"1s"`.
|
|
481
|
+
* @returns {RateLimiter}
|
|
482
|
+
*/
|
|
483
|
+
function rateLimit(max, interval) {
|
|
484
|
+
const perWindow = Math.max(1, Math.floor(max));
|
|
485
|
+
const windowMs = toMs(interval, "interval");
|
|
486
|
+
/** @type {number[]} */
|
|
487
|
+
const starts = [];
|
|
488
|
+
/** @type {_Fifo<() => void>} */
|
|
489
|
+
const waiting = new _Fifo();
|
|
490
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
491
|
+
let timer;
|
|
492
|
+
|
|
493
|
+
const pump = () => {
|
|
494
|
+
timer = undefined;
|
|
495
|
+
while (waiting.size) {
|
|
496
|
+
const now = Date.now();
|
|
497
|
+
while (starts.length && now - starts[0] >= windowMs) starts.shift();
|
|
498
|
+
if (starts.length >= perWindow) {
|
|
499
|
+
timer = setTimeout(pump, Math.max(1, starts[0] + windowMs - now));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
starts.push(now);
|
|
503
|
+
/** @type {() => void} */ (waiting.shift())();
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
/** @type {any} */
|
|
507
|
+
const limiter = (/** @type {() => unknown} */ fn) =>
|
|
508
|
+
new Promise((resolve, reject) => {
|
|
509
|
+
waiting.push(() => void Promise.resolve().then(fn).then(resolve, reject));
|
|
510
|
+
if (timer === undefined) pump();
|
|
511
|
+
});
|
|
512
|
+
Object.defineProperty(limiter, "pendingCount", { get: () => waiting.size });
|
|
513
|
+
return limiter;
|
|
514
|
+
}
|
|
515
|
+
|
|
310
516
|
/**
|
|
311
517
|
* A task queue with a concurrency limit, priorities, pause and per-task
|
|
312
518
|
* timeouts.
|
|
@@ -326,15 +532,15 @@ function queue(options = {}) {
|
|
|
326
532
|
let running = 0;
|
|
327
533
|
let order = 0;
|
|
328
534
|
const taskTimeout = _ms(options.timeout);
|
|
329
|
-
/** @type {
|
|
330
|
-
const waiting =
|
|
535
|
+
/** @type {_Heap<{ run: () => void, priority: number, order: number }>} */
|
|
536
|
+
const waiting = new _Heap();
|
|
331
537
|
/** @type {Array<() => void>} */
|
|
332
538
|
let idle = [];
|
|
333
539
|
/** @type {Array<() => void>} */
|
|
334
540
|
let empty = [];
|
|
335
541
|
|
|
336
542
|
const check = () => {
|
|
337
|
-
if (!waiting.
|
|
543
|
+
if (!waiting.size) {
|
|
338
544
|
for (const resolve of empty) resolve();
|
|
339
545
|
empty = [];
|
|
340
546
|
if (running === 0) {
|
|
@@ -344,8 +550,8 @@ function queue(options = {}) {
|
|
|
344
550
|
}
|
|
345
551
|
};
|
|
346
552
|
const pump = () => {
|
|
347
|
-
while (!paused && running < concurrency && waiting.
|
|
348
|
-
const task = /** @type {{ run: () => void }} */ (waiting.
|
|
553
|
+
while (!paused && running < concurrency && waiting.size) {
|
|
554
|
+
const task = /** @type {{ run: () => void }} */ (waiting.pop());
|
|
349
555
|
running++;
|
|
350
556
|
task.run();
|
|
351
557
|
}
|
|
@@ -369,9 +575,7 @@ function queue(options = {}) {
|
|
|
369
575
|
});
|
|
370
576
|
},
|
|
371
577
|
};
|
|
372
|
-
|
|
373
|
-
if (index === -1) waiting.push(entry);
|
|
374
|
-
else waiting.splice(index, 0, entry);
|
|
578
|
+
waiting.push(entry);
|
|
375
579
|
pump();
|
|
376
580
|
});
|
|
377
581
|
},
|
|
@@ -388,17 +592,17 @@ function queue(options = {}) {
|
|
|
388
592
|
return self;
|
|
389
593
|
},
|
|
390
594
|
clear() {
|
|
391
|
-
waiting.
|
|
595
|
+
waiting.clear();
|
|
392
596
|
check();
|
|
393
597
|
},
|
|
394
598
|
onIdle() {
|
|
395
|
-
return !waiting.
|
|
599
|
+
return !waiting.size && running === 0 ? Promise.resolve() : new Promise((resolve) => idle.push(() => resolve(undefined)));
|
|
396
600
|
},
|
|
397
601
|
onEmpty() {
|
|
398
|
-
return !waiting.
|
|
602
|
+
return !waiting.size ? Promise.resolve() : new Promise((resolve) => empty.push(() => resolve(undefined)));
|
|
399
603
|
},
|
|
400
604
|
get size() {
|
|
401
|
-
return waiting.
|
|
605
|
+
return waiting.size;
|
|
402
606
|
},
|
|
403
607
|
get pending() {
|
|
404
608
|
return running;
|
|
@@ -527,12 +731,7 @@ async function settle(promises) {
|
|
|
527
731
|
async function props(object) {
|
|
528
732
|
const keys = Object.keys(object);
|
|
529
733
|
const values = await Promise.all(keys.map((key) => object[key]));
|
|
530
|
-
/** @type {any} */
|
|
531
|
-
const out = {};
|
|
532
|
-
keys.forEach((key, i) => {
|
|
533
|
-
out[key] = values[i];
|
|
534
|
-
});
|
|
535
|
-
return out;
|
|
734
|
+
return /** @type {any} */ (Object.fromEntries(keys.map((key, i) => [key, values[i]])));
|
|
536
735
|
}
|
|
537
736
|
|
|
538
737
|
/**
|
|
@@ -553,33 +752,66 @@ async function poll(fn, options = {}) {
|
|
|
553
752
|
const interval = _ms(options.interval) ?? 100;
|
|
554
753
|
const limitMs = _ms(options.timeout);
|
|
555
754
|
const deadline = limitMs === undefined ? Infinity : Date.now() + limitMs;
|
|
755
|
+
/** @type {unknown} */
|
|
756
|
+
let lastError;
|
|
556
757
|
for (;;) {
|
|
557
758
|
if (options.signal?.aborted) throw new AbortError(undefined, { cause: options.signal.reason });
|
|
558
759
|
try {
|
|
559
760
|
const value = await fn();
|
|
560
761
|
if (value) return /** @type {NonNullable<Awaited<T>>} */ (value);
|
|
561
|
-
|
|
562
|
-
|
|
762
|
+
lastError = undefined;
|
|
763
|
+
} catch (error) {
|
|
764
|
+
// Not ready yet. Kept, so a timeout can say why.
|
|
765
|
+
lastError = error;
|
|
563
766
|
}
|
|
564
767
|
const remaining = deadline - Date.now();
|
|
565
|
-
if (remaining <= 0) throw new TimeoutError(`poll() timed out after ${limitMs}ms`, { timeout: limitMs });
|
|
768
|
+
if (remaining <= 0) throw new TimeoutError(`poll() timed out after ${limitMs}ms`, { timeout: limitMs, cause: lastError });
|
|
566
769
|
await sleep(Math.min(interval, remaining), { signal: options.signal });
|
|
567
770
|
}
|
|
568
771
|
}
|
|
569
772
|
|
|
773
|
+
/**
|
|
774
|
+
* Makes any promise give up when a signal fires. The work behind it keeps
|
|
775
|
+
* running unless it listens to the signal too.
|
|
776
|
+
*
|
|
777
|
+
* @example
|
|
778
|
+
* const controller = new AbortController();
|
|
779
|
+
* const rows = await async.abortable(slowQuery(), controller.signal);
|
|
780
|
+
*
|
|
781
|
+
* @template T
|
|
782
|
+
* @param {T | PromiseLike<T>} promise
|
|
783
|
+
* @param {AbortSignal} [signal]
|
|
784
|
+
* @returns {Promise<Awaited<T>>}
|
|
785
|
+
* @throws {AbortError} When the signal fires first.
|
|
786
|
+
*/
|
|
787
|
+
function abortable(promise, signal) {
|
|
788
|
+
if (!signal) return /** @type {Promise<Awaited<T>>} */ (Promise.resolve(promise));
|
|
789
|
+
if (signal.aborted) return Promise.reject(new AbortError(undefined, { cause: signal.reason }));
|
|
790
|
+
return new Promise((resolve, reject) => {
|
|
791
|
+
const onAbort = () => reject(new AbortError(undefined, { cause: signal.reason }));
|
|
792
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
793
|
+
Promise.resolve(promise)
|
|
794
|
+
.then(/** @type {any} */ (resolve), reject)
|
|
795
|
+
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
570
799
|
module.exports = {
|
|
571
800
|
sleep,
|
|
572
801
|
map,
|
|
573
802
|
forEach,
|
|
574
803
|
filter,
|
|
575
804
|
series,
|
|
805
|
+
parallel,
|
|
576
806
|
limit,
|
|
807
|
+
rateLimit,
|
|
577
808
|
queue,
|
|
578
809
|
mutex,
|
|
579
810
|
deferred,
|
|
580
811
|
settle,
|
|
581
812
|
props,
|
|
582
813
|
poll,
|
|
814
|
+
abortable,
|
|
583
815
|
retry,
|
|
584
816
|
timeout,
|
|
585
817
|
};
|