support AsyncIterator#return inside AsyncIterableObject and AsyncIterableSource (#209971)

This commit is contained in:
Johannes Rieken 2024-04-09 19:35:16 +02:00 committed by GitHub
parent 6a28cd9073
commit 9c98056dd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 134 additions and 3 deletions

View file

@ -1818,12 +1818,14 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
private _state: AsyncIterableSourceState;
private _results: T[];
private _error: Error | null;
private readonly _onReturn?: () => void | Promise<void>;
private readonly _onStateChanged: Emitter<void>;
constructor(executor: AsyncIterableExecutor<T>) {
constructor(executor: AsyncIterableExecutor<T>, onReturn?: () => void | Promise<void>) {
this._state = AsyncIterableSourceState.Initial;
this._results = [];
this._error = null;
this._onReturn = onReturn;
this._onStateChanged = new Emitter<void>();
queueMicrotask(async () => {
@ -1861,6 +1863,10 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
}
await Event.toPromise(this._onStateChanged.event);
} while (true);
},
return: async () => {
this._onReturn?.();
return { done: true, value: undefined };
}
};
}
@ -2020,7 +2026,13 @@ export class AsyncIterableSource<T> {
private _errorFn: (error: Error) => void;
private _emitFn: (item: T) => void;
constructor() {
/**
*
* @param onReturn A function that will be called when consuming the async iterable
* has finished by the consumer, e.g the for-await-loop has be existed (break, return) early.
* This is NOT called when resolving this source by its owner.
*/
constructor(onReturn?: () => Promise<void> | void) {
this._asyncIterable = new AsyncIterableObject(emitter => {
if (earlyError) {
@ -2033,7 +2045,7 @@ export class AsyncIterableSource<T> {
this._errorFn = (error: Error) => emitter.reject(error);
this._emitFn = (item: T) => emitter.emitOne(item);
return this._deferred.p;
});
}, onReturn);
let earlyError: Error | undefined;
let earlyItems: T[] | undefined;

View file

@ -1558,4 +1558,123 @@ suite('Async', () => {
assert.strictEqual(counter, 4);
});
});
suite('AsyncIterableObject', function () {
test('onReturn NOT called', async function () {
let calledOnReturn = false;
const iter = new async.AsyncIterableObject<number>(writer => {
writer.emitMany([1, 2, 3, 4, 5]);
}, () => {
calledOnReturn = true;
});
for await (const item of iter) {
assert.strictEqual(typeof item, 'number');
}
assert.strictEqual(calledOnReturn, false);
});
test('onReturn called on break', async function () {
let calledOnReturn = false;
const iter = new async.AsyncIterableObject<number>(writer => {
writer.emitMany([1, 2, 3, 4, 5]);
}, () => {
calledOnReturn = true;
});
for await (const item of iter) {
assert.strictEqual(item, 1);
break;
}
assert.strictEqual(calledOnReturn, true);
});
test('onReturn called on return', async function () {
let calledOnReturn = false;
const iter = new async.AsyncIterableObject<number>(writer => {
writer.emitMany([1, 2, 3, 4, 5]);
}, () => {
calledOnReturn = true;
});
await (async function test() {
for await (const item of iter) {
assert.strictEqual(item, 1);
return;
}
})();
assert.strictEqual(calledOnReturn, true);
});
test('onReturn called on throwing', async function () {
let calledOnReturn = false;
const iter = new async.AsyncIterableObject<number>(writer => {
writer.emitMany([1, 2, 3, 4, 5]);
}, () => {
calledOnReturn = true;
});
try {
for await (const item of iter) {
assert.strictEqual(item, 1);
throw new Error();
}
} catch (e) {
}
assert.strictEqual(calledOnReturn, true);
});
});
suite('AsyncIterableSource', function () {
test('onReturn is wired up', async function () {
let calledOnReturn = false;
const source = new async.AsyncIterableSource<number>(() => { calledOnReturn = true; });
source.emitOne(1);
source.emitOne(2);
source.emitOne(3);
source.resolve();
for await (const item of source.asyncIterable) {
assert.strictEqual(item, 1);
break;
}
assert.strictEqual(calledOnReturn, true);
});
test('onReturn is wired up 2', async function () {
let calledOnReturn = false;
const source = new async.AsyncIterableSource<number>(() => { calledOnReturn = true; });
source.emitOne(1);
source.emitOne(2);
source.emitOne(3);
source.resolve();
for await (const item of source.asyncIterable) {
assert.strictEqual(typeof item, 'number');
}
assert.strictEqual(calledOnReturn, false);
});
});
});