@@ -5,6 +5,9 @@ import { expectPromise } from '../../__testUtils__/expectPromise.js';
55
66import { withConcurrentAbruptClose } from '../withConcurrentAbruptClose.js' ;
77
8+ const asyncDispose : typeof Symbol . asyncDispose =
9+ Symbol . asyncDispose ?? Symbol . for ( 'Symbol.asyncDispose' ) ;
10+
811/* eslint-disable @typescript-eslint/require-await */
912describe ( 'withConcurrentAbruptClose' , ( ) => {
1013 it ( 'calls function when returned' , async ( ) => {
@@ -164,6 +167,166 @@ describe('withConcurrentAbruptClose', () => {
164167 expect ( returned ) . to . equal ( true ) ;
165168 } ) ;
166169
170+ it ( 'calls the abrupt-close function at most once before completion is observed' , async ( ) => {
171+ let resolveNext ! : ( result : IteratorResult < number , void > ) => void ;
172+ const nextPromise = new Promise < IteratorResult < number , void > > ( ( resolve ) => {
173+ resolveNext = resolve ;
174+ } ) ;
175+
176+ const source : AsyncGenerator < number , void , void > = {
177+ [ Symbol . asyncIterator ] ( ) {
178+ return this ;
179+ } ,
180+ next ( ) : Promise < IteratorResult < number , void > > {
181+ return nextPromise ;
182+ } ,
183+ return ( ) : Promise < IteratorResult < number , void > > {
184+ return Promise . resolve ( { done : true , value : undefined } ) ;
185+ } ,
186+ throw ( reason ?: unknown ) : Promise < IteratorResult < number , void > > {
187+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
188+ return Promise . reject ( reason ) ;
189+ } ,
190+ async [ asyncDispose ] ( ) {
191+ await this . return ( ) ;
192+ } ,
193+ } ;
194+
195+ let cleanupCalls = 0 ;
196+ const generator = withConcurrentAbruptClose ( source , ( ) => {
197+ cleanupCalls += 1 ;
198+ } ) ;
199+
200+ const pendingNext = generator . next ( ) ;
201+ await generator . return ( ) ;
202+ await generator [ asyncDispose ] ( ) ;
203+
204+ resolveNext ( { done : true , value : undefined } ) ;
205+ await pendingNext ;
206+
207+ expect ( cleanupCalls ) . to . equal ( 1 ) ;
208+ } ) ;
209+
210+ it ( 'does not call cleanup function again when returned after completion' , async ( ) => {
211+ let returned = false ;
212+
213+ const source : AsyncGenerator < number , void , void > = {
214+ [ Symbol . asyncIterator ] ( ) {
215+ return this ;
216+ } ,
217+ next ( ) : Promise < IteratorResult < number , void > > {
218+ return Promise . resolve ( { done : true , value : undefined } ) ;
219+ } ,
220+ return ( ) : Promise < IteratorResult < number , void > > {
221+ returned = true ;
222+ return Promise . resolve ( { done : true , value : undefined } ) ;
223+ } ,
224+ throw ( reason ?: unknown ) : Promise < IteratorResult < number , void > > {
225+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
226+ return Promise . reject ( reason ) ;
227+ } ,
228+ async [ asyncDispose ] ( ) {
229+ await this . return ( ) ;
230+ } ,
231+ } ;
232+
233+ let called = false ;
234+ const generator = withConcurrentAbruptClose ( source , ( ) => {
235+ called = true ;
236+ } ) ;
237+
238+ expect ( await generator . next ( ) ) . to . deep . equal ( {
239+ value : undefined ,
240+ done : true ,
241+ } ) ;
242+ expect ( await generator . return ( ) ) . to . deep . equal ( {
243+ value : undefined ,
244+ done : true ,
245+ } ) ;
246+
247+ expect ( called ) . to . equal ( false ) ;
248+ expect ( returned ) . to . equal ( true ) ;
249+ } ) ;
250+
251+ it ( 'does not call cleanup function again when thrown after completion' , async ( ) => {
252+ let thrownReason : unknown ;
253+
254+ const source : AsyncGenerator < number , void , void > = {
255+ [ Symbol . asyncIterator ] ( ) {
256+ return this ;
257+ } ,
258+ next ( ) : Promise < IteratorResult < number , void > > {
259+ return Promise . resolve ( { done : true , value : undefined } ) ;
260+ } ,
261+ return ( ) : Promise < IteratorResult < number , void > > {
262+ return Promise . resolve ( { done : true , value : undefined } ) ;
263+ } ,
264+ throw ( reason ?: unknown ) : Promise < IteratorResult < number , void > > {
265+ thrownReason = reason ;
266+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
267+ return Promise . reject ( reason ) ;
268+ } ,
269+ async [ Symbol . asyncDispose ] ( ) {
270+ await this . return ( ) ;
271+ } ,
272+ } ;
273+
274+ let called = false ;
275+ const generator = withConcurrentAbruptClose ( source , ( ) => {
276+ called = true ;
277+ } ) ;
278+
279+ expect ( await generator . next ( ) ) . to . deep . equal ( {
280+ value : undefined ,
281+ done : true ,
282+ } ) ;
283+
284+ const oops = new Error ( 'Oops' ) ;
285+ await expectPromise ( generator . throw ( oops ) ) . toRejectWith ( 'Oops' ) ;
286+
287+ expect ( called ) . to . equal ( false ) ;
288+ expect ( thrownReason ) . to . equal ( oops ) ;
289+ } ) ;
290+
291+ it ( 'does not call cleanup function again when disposed after completion' , async ( ) => {
292+ let returned = false ;
293+
294+ const source : AsyncGenerator < number , void , void > = {
295+ [ Symbol . asyncIterator ] ( ) {
296+ return this ;
297+ } ,
298+ next ( ) : Promise < IteratorResult < number , void > > {
299+ return Promise . resolve ( { done : true , value : undefined } ) ;
300+ } ,
301+ return ( ) : Promise < IteratorResult < number , void > > {
302+ returned = true ;
303+ return Promise . resolve ( { done : true , value : undefined } ) ;
304+ } ,
305+ throw ( reason ?: unknown ) : Promise < IteratorResult < number , void > > {
306+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
307+ return Promise . reject ( reason ) ;
308+ } ,
309+ async [ Symbol . asyncDispose ] ( ) {
310+ await this . return ( ) ;
311+ } ,
312+ } ;
313+
314+ let called = false ;
315+ {
316+ await using generator = withConcurrentAbruptClose ( source , ( ) => {
317+ called = true ;
318+ } ) ;
319+
320+ expect ( await generator . next ( ) ) . to . deep . equal ( {
321+ value : undefined ,
322+ done : true ,
323+ } ) ;
324+ }
325+
326+ expect ( called ) . to . equal ( false ) ;
327+ expect ( returned ) . to . equal ( true ) ;
328+ } ) ;
329+
167330 it ( 'returns the generator itself when the `Symbol.asyncIterator` method is called' , async ( ) => {
168331 async function * source ( ) {
169332 yield 1 ;
0 commit comments