Skip to content

Commit 01ac8ce

Browse files
committed
Apply the same robustness fix to filterMap, merge and combine
1 parent 67e406c commit 01ac8ce

13 files changed

Lines changed: 734 additions & 699 deletions

module/dist/ai-ui.cjs

Lines changed: 131 additions & 126 deletions
Large diffs are not rendered by default.

module/dist/ai-ui.js

Lines changed: 131 additions & 126 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

module/dist/ai-ui.min.cjs

Lines changed: 13 additions & 13 deletions
Large diffs are not rendered by default.

module/dist/ai-ui.min.js

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

module/dist/ai-ui.min.mjs

Lines changed: 13 additions & 13 deletions
Large diffs are not rendered by default.

module/dist/ai-ui.mjs

Lines changed: 131 additions & 126 deletions
Large diffs are not rendered by default.

module/esm/ai-ui.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isPromiseLike } from './deferred.js';
2-
import { Ignore, asyncIterator, defineIterableProperty, isAsyncIter, isAsyncIterator } from './iterators.js';
2+
import { Ignore, asyncIterator, defineIterableProperty, isAsyncIter } from './iterators.js';
33
import { when } from './when.js';
44
import { DEBUG, console, timeOutWarn } from './debug.js';
55
import { callStackSymbol } from './tags.js';
@@ -96,7 +96,7 @@ export const tag = function (_1, _2, _3) {
9696
...Object.getOwnPropertyDescriptor(Element.prototype, 'attributes'),
9797
set(a) {
9898
if (isAsyncIter(a)) {
99-
const ai = isAsyncIterator(a) ? a : a[Symbol.asyncIterator]();
99+
const ai = asyncIterator(a);
100100
const step = () => ai.next().then(({ done, value }) => { assignProps(this, value); done || step(); }, ex => console.warn(ex));
101101
step();
102102
}
@@ -245,7 +245,7 @@ export const tag = function (_1, _2, _3) {
245245
}
246246
if (isAsyncIter(c)) {
247247
const insertionStack = DEBUG ? ('\n' + new Error().stack?.replace(/^Error: /, "Insertion :")) : '';
248-
let ap = isAsyncIterator(c) ? c : c[Symbol.asyncIterator]();
248+
let ap = asyncIterator(c);
249249
let notYetMounted = true;
250250
const terminateSource = (force = false) => {
251251
if (!ap || !replacement.nodes)

module/esm/iterators.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export interface QueueIteratableIterator<T> extends AsyncIterableIterator<T>, As
2626
}
2727
export interface AsyncExtraIterable<T> extends AsyncIterable<T>, AsyncIterableHelpers {
2828
}
29-
export declare function isAsyncIterator<T = unknown>(o: any | AsyncIterator<T>): o is AsyncIterator<T>;
30-
export declare function isAsyncIterable<T = unknown>(o: any | AsyncIterable<T>): o is AsyncIterable<T>;
3129
export declare function isAsyncIter<T = unknown>(o: any | AsyncIterable<T> | AsyncIterator<T>): o is AsyncIterable<T> | AsyncIterator<T>;
3230
export type AsyncProvider<T> = AsyncIterator<T> | AsyncIterable<T>;
3331
export declare function asyncIterator<T>(o: AsyncProvider<T>): AsyncIterator<T, any, any>;

module/esm/iterators.js

Lines changed: 145 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { DEBUG, console } from "./debug.js";
22
import { deferred, isObjectLike, isPromiseLike } from "./deferred.js";
33
export const Iterability = Symbol("Iterability");
44
// NB: This also (incorrectly) passes sync iterators, as the protocol names are the same
5-
export function isAsyncIterator(o) {
5+
function isAsyncIterator(o) {
66
return isObjectLike(o) && 'next' in o && typeof o?.next === 'function';
77
}
8-
export function isAsyncIterable(o) {
8+
function isAsyncIterable(o) {
99
return isObjectLike(o) && (Symbol.asyncIterator in o) && typeof o[Symbol.asyncIterator] === 'function';
1010
}
1111
export function isAsyncIter(o) {
@@ -367,47 +367,51 @@ export const merge = (...ai) => {
367367
};
368368
const results = new Array(ai.length);
369369
const merged = {
370-
[Symbol.asyncIterator]() { return merged; },
371-
next() {
372-
init();
373-
return promises.size
374-
? Promise.race(promises.values()).then(({ key, result }) => {
375-
if (result.done) {
376-
promises.delete(key);
377-
it.delete(key);
378-
results[key] = result.value;
379-
return merged.next();
370+
[Symbol.asyncIterator]() {
371+
return {
372+
__proto__: merged,
373+
next() {
374+
init();
375+
return promises.size
376+
? Promise.race(promises.values()).then(({ key, result }) => {
377+
if (result.done) {
378+
promises.delete(key);
379+
it.delete(key);
380+
results[key] = result.value;
381+
return this.next();
382+
}
383+
else {
384+
promises.set(key, it.has(key)
385+
? it.get(key).next().then(result => ({ key, result })).catch(ex => ({ key, result: { done: true, value: ex } }))
386+
: Promise.resolve({ key, result: { done: true, value: undefined } }));
387+
return result;
388+
}
389+
}).catch(ex => {
390+
// `ex` is the underlying async iteration exception
391+
return this.throw(ex); // ?? Promise.reject({ done: true as const, value: new Error("Iterator merge exception") });
392+
})
393+
: Promise.resolve({ done: true, value: results });
394+
},
395+
async return(r) {
396+
for (const key of it.keys()) {
397+
if (promises.has(key)) {
398+
promises.delete(key);
399+
results[key] = await it.get(key)?.return?.({ done: true, value: r }).then(v => v.value, ex => ex);
400+
}
380401
}
381-
else {
382-
promises.set(key, it.has(key)
383-
? it.get(key).next().then(result => ({ key, result })).catch(ex => ({ key, result: { done: true, value: ex } }))
384-
: Promise.resolve({ key, result: { done: true, value: undefined } }));
385-
return result;
402+
return { done: true, value: results };
403+
},
404+
async throw(ex) {
405+
for (const key of it.keys()) {
406+
if (promises.has(key)) {
407+
promises.delete(key);
408+
results[key] = await it.get(key)?.throw?.(ex).then(v => v.value, ex => ex);
409+
}
386410
}
387-
}).catch(ex => {
388-
// `ex` is the underlying async iteration exception
389-
return merged.throw(ex); // ?? Promise.reject({ done: true as const, value: new Error("Iterator merge exception") });
390-
})
391-
: Promise.resolve({ done: true, value: results });
392-
},
393-
async return(r) {
394-
for (const key of it.keys()) {
395-
if (promises.has(key)) {
396-
promises.delete(key);
397-
results[key] = await it.get(key)?.return?.({ done: true, value: r }).then(v => v.value, ex => ex);
398-
}
399-
}
400-
return { done: true, value: results };
401-
},
402-
async throw(ex) {
403-
for (const key of it.keys()) {
404-
if (promises.has(key)) {
405-
promises.delete(key);
406-
results[key] = await it.get(key)?.throw?.(ex).then(v => v.value, ex => ex);
411+
// Because we've passed the exception on to all the sources, we're now done
412+
return { done: true, value: results };
407413
}
408-
}
409-
// Because we've passed the exception on to all the sources, we're now done
410-
return { done: true, value: results };
414+
};
411415
}
412416
};
413417
return iterableHelpers(merged);
@@ -417,47 +421,51 @@ export const combine = (src, opts = {}) => {
417421
const si = new Map();
418422
let pc; // Initialized lazily
419423
const ci = {
420-
[Symbol.asyncIterator]() { return ci; },
421-
next() {
422-
if (pc === undefined) {
423-
pc = new Map(Object.entries(src).map(([k, sit]) => {
424-
const source = sit[Symbol.asyncIterator]();
425-
si.set(k, source);
426-
return [k, source.next().then(ir => ({ si, k, ir }))];
427-
}));
428-
}
429-
return (function step() {
430-
return Promise.race(pc.values()).then(({ k, ir }) => {
431-
if (ir.done) {
432-
pc.delete(k);
433-
si.delete(k);
434-
if (!pc.size)
435-
return { done: true, value: undefined };
436-
return step();
437-
}
438-
else {
439-
accumulated[k] = ir.value;
440-
pc.set(k, si.get(k).next().then(ir => ({ k, ir })));
424+
[Symbol.asyncIterator]() {
425+
return {
426+
__proto__: ci,
427+
next() {
428+
if (pc === undefined) {
429+
pc = new Map(Object.entries(src).map(([k, sit]) => {
430+
const source = sit[Symbol.asyncIterator]();
431+
si.set(k, source);
432+
return [k, source.next().then(ir => ({ si, k, ir }))];
433+
}));
441434
}
442-
if (opts.ignorePartial) {
443-
if (Object.keys(accumulated).length < Object.keys(src).length)
444-
return step();
435+
return (function step() {
436+
return Promise.race(pc.values()).then(({ k, ir }) => {
437+
if (ir.done) {
438+
pc.delete(k);
439+
si.delete(k);
440+
if (!pc.size)
441+
return { done: true, value: undefined };
442+
return step();
443+
}
444+
else {
445+
accumulated[k] = ir.value;
446+
pc.set(k, si.get(k).next().then(ir => ({ k, ir })));
447+
}
448+
if (opts.ignorePartial) {
449+
if (Object.keys(accumulated).length < Object.keys(src).length)
450+
return step();
451+
}
452+
return { done: false, value: accumulated };
453+
});
454+
})();
455+
},
456+
return(v) {
457+
for (const ai of si.values()) {
458+
ai.return?.(v);
445459
}
446-
return { done: false, value: accumulated };
447-
});
448-
})();
449-
},
450-
return(v) {
451-
for (const ai of si.values()) {
452-
ai.return?.(v);
453-
}
454-
;
455-
return Promise.resolve({ done: true, value: v });
456-
},
457-
throw(ex) {
458-
for (const ai of si.values())
459-
ai.throw?.(ex);
460-
return Promise.resolve({ done: true, value: ex });
460+
;
461+
return Promise.resolve({ done: true, value: v });
462+
},
463+
throw(ex) {
464+
for (const ai of si.values())
465+
ai.throw?.(ex);
466+
return Promise.resolve({ done: true, value: ex });
467+
}
468+
};
461469
}
462470
};
463471
return iterableHelpers(ci);
@@ -511,60 +519,62 @@ export function filterMap(source, fn, initialValue = Ignore, prev = Ignore) {
511519
}
512520
let fai = {
513521
[Symbol.asyncIterator]() {
514-
return fai;
515-
},
516-
next(...args) {
517-
if (initialValue !== Ignore) {
518-
if (isPromiseLike(initialValue)) {
519-
const init = initialValue.then(value => ({ done: false, value }));
520-
initialValue = Ignore;
521-
return init;
522-
}
523-
else {
524-
const init = Promise.resolve({ done: false, value: initialValue });
525-
initialValue = Ignore;
526-
return init;
527-
}
528-
}
529-
return new Promise(function step(resolve, reject) {
530-
if (!ai)
531-
ai = source[Symbol.asyncIterator]();
532-
ai.next(...args).then(p => p.done
533-
? (prev = Ignore, resolve(p))
534-
: resolveSync(fn(p.value, prev), f => f === Ignore
535-
? step(resolve, reject)
536-
: resolve({ done: false, value: prev = f }), ex => {
537-
prev = Ignore; // Remove reference for GC
538-
// The filter function failed. We check ai here as it might have been terminated already
539-
const sourceResponse = ai?.throw?.(ex) ?? ai?.return?.(ex);
540-
// Terminate the source (ai) and consumer (reject)
541-
if (isPromiseLike(sourceResponse))
542-
sourceResponse.then(reject, reject);
543-
else
522+
return {
523+
__proto__: fai,
524+
next(...args) {
525+
if (initialValue !== Ignore) {
526+
if (isPromiseLike(initialValue)) {
527+
const init = initialValue.then(value => ({ done: false, value }));
528+
initialValue = Ignore;
529+
return init;
530+
}
531+
else {
532+
const init = Promise.resolve({ done: false, value: initialValue });
533+
initialValue = Ignore;
534+
return init;
535+
}
536+
}
537+
return new Promise(function step(resolve, reject) {
538+
if (!ai)
539+
ai = source[Symbol.asyncIterator]();
540+
ai.next(...args).then(p => p.done
541+
? (prev = Ignore, resolve(p))
542+
: resolveSync(fn(p.value, prev), f => f === Ignore
543+
? step(resolve, reject)
544+
: resolve({ done: false, value: prev = f }), ex => {
545+
prev = Ignore; // Remove reference for GC
546+
// The filter function failed. We check ai here as it might have been terminated already
547+
const sourceResponse = ai?.throw?.(ex) ?? ai?.return?.(ex);
548+
// Terminate the source (ai) and consumer (reject)
549+
if (isPromiseLike(sourceResponse))
550+
sourceResponse.then(reject, reject);
551+
else
552+
reject({ done: true, value: ex });
553+
}), ex => {
554+
// The source threw. Tell the consumer
555+
prev = Ignore; // Remove reference for GC
544556
reject({ done: true, value: ex });
545-
}), ex => {
546-
// The source threw. Tell the consumer
547-
prev = Ignore; // Remove reference for GC
548-
reject({ done: true, value: ex });
549-
}).catch(ex => {
550-
// The callback threw
551-
prev = Ignore; // Remove reference for GC
552-
const sourceResponse = ai.throw?.(ex) ?? ai.return?.(ex);
553-
// Terminate the source (ai) and consumer (reject)
554-
if (isPromiseLike(sourceResponse))
555-
sourceResponse.then(reject, reject);
556-
else
557-
reject({ done: true, value: sourceResponse });
558-
});
559-
});
560-
},
561-
throw(ex) {
562-
// The consumer wants us to exit with an exception. Tell the source
563-
return Promise.resolve(ai?.throw?.(ex) ?? ai?.return?.(ex)).then(done, done);
564-
},
565-
return(v) {
566-
// The consumer told us to return, so we need to terminate the source
567-
return Promise.resolve(ai?.return?.(v)).then(done, done);
557+
}).catch(ex => {
558+
// The callback threw
559+
prev = Ignore; // Remove reference for GC
560+
const sourceResponse = ai.throw?.(ex) ?? ai.return?.(ex);
561+
// Terminate the source (ai) and consumer (reject)
562+
if (isPromiseLike(sourceResponse))
563+
sourceResponse.then(reject, reject);
564+
else
565+
reject({ done: true, value: sourceResponse });
566+
});
567+
});
568+
},
569+
throw(ex) {
570+
// The consumer wants us to exit with an exception. Tell the source
571+
return Promise.resolve(ai?.throw?.(ex) ?? ai?.return?.(ex)).then(done, done);
572+
},
573+
return(v) {
574+
// The consumer told us to return, so we need to terminate the source
575+
return Promise.resolve(ai?.return?.(v)).then(done, done);
576+
}
577+
};
568578
}
569579
};
570580
return iterableHelpers(fai);
@@ -623,7 +633,8 @@ function multi() {
623633
}
624634
let mai = {
625635
[Symbol.asyncIterator]() {
626-
return Object.create(mai, Object.getOwnPropertyDescriptors({
636+
return {
637+
__proto__: mai,
627638
next() {
628639
if (!ai) {
629640
ai = source[Symbol.asyncIterator]();
@@ -650,7 +661,7 @@ function multi() {
650661
return Promise.resolve({ done: true, value: v });
651662
return Promise.resolve(ai?.return?.(v)).then(done, done);
652663
}
653-
}));
664+
};
654665
}
655666
};
656667
return iterableHelpers(mai);

module/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)