Skip to content

Commit 1d87f8e

Browse files
committed
feat!: reconcile abort-signal with type-safety and efficiency refactors
Layers minimal refactors on top of the abort-signal feature commits: - Add normalizeError helper (lib/misc.js) and use it in all 5 fillQueue / fail-fast catch sites - Extract isValueObject helper in lib/type-checks.js (DRY for isIterable / isAsyncIterable) - Add bounds check to ordered-insertion while loop in fillQueue so the BufferPromise type cast is honest past array end - Inline null-safety on the IteratorResult shape check (`!result || typeof result !== 'object'`); typeof null === 'object' would have let null through to the next-line property accesses Drops the larger-scope tweaks from the harden / efficiency branches that weren't earning their keep: - @voxpelli/typed-utils runtime dep (kept the lib zero-dep; the only material correctness win — null check on IteratorResult — is inlined) - yieldArrayWithItem generator (replaces a 1-element array allocation in the common case with a generator allocation; not a measurable win and less readable than the spread) - guardedArrayIncludes (the type cast in isPartOfArray is honest enough) BREAKING CHANGE: engine requirement bumped from >=18.6.0 to >=22.0.0 (native Symbol.asyncDispose is required). Callback signature widened from `(item)` to `(item, { signal })`; existing one-arg callbacks keep working since JS ignores extra args, but TypeScript consumers that pass a callback type with a strict single-parameter signature may need to update the type.
1 parent 11fbb41 commit 1d87f8e

3 files changed

Lines changed: 26 additions & 11 deletions

File tree

index.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
// TODO: THERE'S ACTUALLY A "throw" method MENTION IN https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation: "NOTE: Exceptions from the inner iterator throw method are propagated. Normal completions from an inner throw method are processed similarly to an inner next." THOUGH NOT SURE HOW TO TRIGGER IT IN PRACTICE, SEE yield.spec.js
77

88
import { findLeastTargeted } from './lib/find-least-targeted.js';
9-
import { arrayDeleteInPlace, makeIterableAsync } from './lib/misc.js';
10-
import { isAsyncIterable, isIterable, isPartOfArray } from './lib/type-checks.js';
9+
import { arrayDeleteInPlace, makeIterableAsync, normalizeError } from './lib/misc.js';
10+
import { isAsyncIterable, isIterable, isObject, isPartOfArray } from './lib/type-checks.js';
1111

1212
/**
1313
* @template T
@@ -159,10 +159,10 @@ export function bufferedAsyncMap (input, callback, options) {
159159
const bufferPromise = currentSubIterator
160160
? Promise.resolve(currentSubIterator.next())
161161
.catch(err => ({
162-
err: err instanceof Error ? err : new Error('Unknown subiterator error'),
162+
err: normalizeError(err, 'Unknown subiterator error'),
163163
}))
164164
.then(async result => {
165-
if (typeof result !== 'object') {
165+
if (!isObject(result)) {
166166
throw new TypeError('Expected an object value');
167167
}
168168
if ('err' in result || result.done) {
@@ -184,10 +184,10 @@ export function bufferedAsyncMap (input, callback, options) {
184184
})
185185
: Promise.resolve(asyncIterator.next())
186186
.catch(err => ({
187-
err: err instanceof Error ? err : new Error('Unknown iterator error'),
187+
err: normalizeError(err, 'Unknown iterator error'),
188188
}))
189189
.then(async result => {
190-
if (typeof result !== 'object') {
190+
if (!isObject(result)) {
191191
throw new TypeError('Expected an object value');
192192
}
193193
if ('err' in result || result.done) {
@@ -221,7 +221,7 @@ export function bufferedAsyncMap (input, callback, options) {
221221
promiseValue = {
222222
bufferPromise,
223223
done: true,
224-
err: err instanceof Error ? err : new Error('Unknown callback error'),
224+
err: normalizeError(err, 'Unknown callback error'),
225225
value: undefined,
226226
};
227227
}
@@ -234,7 +234,7 @@ export function bufferedAsyncMap (input, callback, options) {
234234
if (ordered && currentSubIterator) {
235235
let i = 0;
236236

237-
while (promisesToSourceIteratorMap.get(/** @type {BufferPromise} */ (bufferedPromises[i])) === currentSubIterator) {
237+
while (i < bufferedPromises.length && promisesToSourceIteratorMap.get(/** @type {BufferPromise} */ (bufferedPromises[i])) === currentSubIterator) {
238238
i += 1;
239239
}
240240

@@ -319,7 +319,7 @@ export function bufferedAsyncMap (input, callback, options) {
319319
return { done: true, value: undefined };
320320
} else if (err || done) {
321321
if (err) {
322-
const normalisedErr = err instanceof Error ? err : new Error('Unknown error');
322+
const normalisedErr = normalizeError(err, 'Unknown error');
323323

324324
// In fail-fast mode the first captured error short-circuits iteration:
325325
// route it through the same abort machinery so the next .next() rejects

lib/misc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ export function arrayDeleteInPlace (list, value) {
2222
list.splice(index, 1);
2323
}
2424
}
25+
26+
/**
27+
* @param {unknown} err
28+
* @param {string} defaultMessage
29+
* @returns {Error}
30+
*/
31+
export function normalizeError (err, defaultMessage) {
32+
return err instanceof Error ? err : new Error(defaultMessage);
33+
}

lib/type-checks.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
/**
2+
* @param {unknown} value
3+
* @returns {value is object}
4+
*/
5+
export const isObject = (value) => Boolean(value && typeof value === 'object');
6+
17
/**
28
* @param {unknown} value
39
* @returns {value is Iterable<unknown>}
410
*/
5-
export const isIterable = (value) => Boolean(value && typeof value === 'object' && Symbol.iterator in value);
11+
export const isIterable = (value) => isObject(value) && Symbol.iterator in value;
612

713
/**
814
* @param {unknown} value
915
* @returns {value is AsyncIterable<unknown>}
1016
*/
11-
export const isAsyncIterable = (value) => Boolean(value && typeof value === 'object' && Symbol.asyncIterator in value);
17+
export const isAsyncIterable = (value) => isObject(value) && Symbol.asyncIterator in value;
1218

1319
/**
1420
* @template Values

0 commit comments

Comments
 (0)