Skip to content

Commit 9c98056

Browse files
authored
support AsyncIterator#return inside AsyncIterableObject and AsyncIterableSource (microsoft#209971)
1 parent 6a28cd9 commit 9c98056

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

src/vs/base/common/async.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,12 +1818,14 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
18181818
private _state: AsyncIterableSourceState;
18191819
private _results: T[];
18201820
private _error: Error | null;
1821+
private readonly _onReturn?: () => void | Promise<void>;
18211822
private readonly _onStateChanged: Emitter<void>;
18221823

1823-
constructor(executor: AsyncIterableExecutor<T>) {
1824+
constructor(executor: AsyncIterableExecutor<T>, onReturn?: () => void | Promise<void>) {
18241825
this._state = AsyncIterableSourceState.Initial;
18251826
this._results = [];
18261827
this._error = null;
1828+
this._onReturn = onReturn;
18271829
this._onStateChanged = new Emitter<void>();
18281830

18291831
queueMicrotask(async () => {
@@ -1861,6 +1863,10 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
18611863
}
18621864
await Event.toPromise(this._onStateChanged.event);
18631865
} while (true);
1866+
},
1867+
return: async () => {
1868+
this._onReturn?.();
1869+
return { done: true, value: undefined };
18641870
}
18651871
};
18661872
}
@@ -2020,7 +2026,13 @@ export class AsyncIterableSource<T> {
20202026
private _errorFn: (error: Error) => void;
20212027
private _emitFn: (item: T) => void;
20222028

2023-
constructor() {
2029+
/**
2030+
*
2031+
* @param onReturn A function that will be called when consuming the async iterable
2032+
* has finished by the consumer, e.g the for-await-loop has be existed (break, return) early.
2033+
* This is NOT called when resolving this source by its owner.
2034+
*/
2035+
constructor(onReturn?: () => Promise<void> | void) {
20242036
this._asyncIterable = new AsyncIterableObject(emitter => {
20252037

20262038
if (earlyError) {
@@ -2033,7 +2045,7 @@ export class AsyncIterableSource<T> {
20332045
this._errorFn = (error: Error) => emitter.reject(error);
20342046
this._emitFn = (item: T) => emitter.emitOne(item);
20352047
return this._deferred.p;
2036-
});
2048+
}, onReturn);
20372049

20382050
let earlyError: Error | undefined;
20392051
let earlyItems: T[] | undefined;

src/vs/base/test/common/async.test.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,4 +1558,123 @@ suite('Async', () => {
15581558
assert.strictEqual(counter, 4);
15591559
});
15601560
});
1561+
1562+
suite('AsyncIterableObject', function () {
1563+
1564+
1565+
test('onReturn NOT called', async function () {
1566+
1567+
let calledOnReturn = false;
1568+
const iter = new async.AsyncIterableObject<number>(writer => {
1569+
writer.emitMany([1, 2, 3, 4, 5]);
1570+
}, () => {
1571+
calledOnReturn = true;
1572+
});
1573+
1574+
for await (const item of iter) {
1575+
assert.strictEqual(typeof item, 'number');
1576+
}
1577+
1578+
assert.strictEqual(calledOnReturn, false);
1579+
1580+
});
1581+
1582+
test('onReturn called on break', async function () {
1583+
1584+
let calledOnReturn = false;
1585+
const iter = new async.AsyncIterableObject<number>(writer => {
1586+
writer.emitMany([1, 2, 3, 4, 5]);
1587+
}, () => {
1588+
calledOnReturn = true;
1589+
});
1590+
1591+
for await (const item of iter) {
1592+
assert.strictEqual(item, 1);
1593+
break;
1594+
}
1595+
1596+
assert.strictEqual(calledOnReturn, true);
1597+
1598+
});
1599+
1600+
test('onReturn called on return', async function () {
1601+
1602+
let calledOnReturn = false;
1603+
const iter = new async.AsyncIterableObject<number>(writer => {
1604+
writer.emitMany([1, 2, 3, 4, 5]);
1605+
}, () => {
1606+
calledOnReturn = true;
1607+
});
1608+
1609+
await (async function test() {
1610+
for await (const item of iter) {
1611+
assert.strictEqual(item, 1);
1612+
return;
1613+
}
1614+
})();
1615+
1616+
1617+
assert.strictEqual(calledOnReturn, true);
1618+
1619+
});
1620+
1621+
1622+
test('onReturn called on throwing', async function () {
1623+
1624+
let calledOnReturn = false;
1625+
const iter = new async.AsyncIterableObject<number>(writer => {
1626+
writer.emitMany([1, 2, 3, 4, 5]);
1627+
}, () => {
1628+
calledOnReturn = true;
1629+
});
1630+
1631+
try {
1632+
for await (const item of iter) {
1633+
assert.strictEqual(item, 1);
1634+
throw new Error();
1635+
}
1636+
} catch (e) {
1637+
1638+
}
1639+
1640+
assert.strictEqual(calledOnReturn, true);
1641+
});
1642+
});
1643+
1644+
suite('AsyncIterableSource', function () {
1645+
1646+
test('onReturn is wired up', async function () {
1647+
let calledOnReturn = false;
1648+
const source = new async.AsyncIterableSource<number>(() => { calledOnReturn = true; });
1649+
1650+
source.emitOne(1);
1651+
source.emitOne(2);
1652+
source.emitOne(3);
1653+
source.resolve();
1654+
1655+
for await (const item of source.asyncIterable) {
1656+
assert.strictEqual(item, 1);
1657+
break;
1658+
}
1659+
1660+
assert.strictEqual(calledOnReturn, true);
1661+
1662+
});
1663+
1664+
test('onReturn is wired up 2', async function () {
1665+
let calledOnReturn = false;
1666+
const source = new async.AsyncIterableSource<number>(() => { calledOnReturn = true; });
1667+
1668+
source.emitOne(1);
1669+
source.emitOne(2);
1670+
source.emitOne(3);
1671+
source.resolve();
1672+
1673+
for await (const item of source.asyncIterable) {
1674+
assert.strictEqual(typeof item, 'number');
1675+
}
1676+
1677+
assert.strictEqual(calledOnReturn, false);
1678+
});
1679+
});
15611680
});

0 commit comments

Comments
 (0)