@@ -108,6 +108,109 @@ async function testMergeSourceError() {
108108 ) ;
109109}
110110
111+ async function testMergeFalsySourceErrors ( ) {
112+ const reasons = [ undefined , null , false , 0 , '' , NaN ] ;
113+
114+ for ( const reason of reasons ) {
115+ const noError = { __proto__ : null } ;
116+ let actual = noError ;
117+ try {
118+ await text ( merge ( rejectedSource ( reason ) , from ( 'other' ) ) ) ;
119+ } catch ( error ) {
120+ actual = error ;
121+ }
122+ assert . strictEqual ( Object . is ( actual , reason ) , true ) ;
123+ }
124+ }
125+
126+ function rejectedSource ( reason ) {
127+ return {
128+ __proto__ : null ,
129+ [ Symbol . asyncIterator ] ( ) {
130+ return this ;
131+ } ,
132+ next ( ) {
133+ return Promise . reject ( reason ) ;
134+ } ,
135+ } ;
136+ }
137+
138+ function pendingSource ( ) {
139+ return {
140+ __proto__ : null ,
141+ [ Symbol . asyncIterator ] ( ) {
142+ return this ;
143+ } ,
144+ next ( ) {
145+ return new Promise ( ( ) => { } ) ;
146+ } ,
147+ return ( ) {
148+ return new Promise ( ( ) => { } ) ;
149+ } ,
150+ } ;
151+ }
152+
153+ async function testMergeSourceErrorDoesNotAwaitCleanup ( ) {
154+ const reason = new Error ( 'source failed' ) ;
155+
156+ const timedOut = { __proto__ : null } ;
157+ const outcome = await Promise . race ( [
158+ text ( merge ( rejectedSource ( reason ) , pendingSource ( ) ) ) . then (
159+ ( ) => ( { __proto__ : null , status : 'fulfilled' } ) ,
160+ ( error ) => ( { __proto__ : null , status : 'rejected' , error } ) ,
161+ ) ,
162+ new Promise ( ( resolve ) => setImmediate ( resolve , timedOut ) ) ,
163+ ] ) ;
164+
165+ assert . notStrictEqual ( outcome , timedOut ) ;
166+ assert . strictEqual ( outcome . status , 'rejected' ) ;
167+ assert . strictEqual ( outcome . error , reason ) ;
168+ }
169+
170+ async function testMergeBreakDoesNotAwaitCleanup ( ) {
171+ async function * readySource ( ) {
172+ yield [ Uint8Array . of ( 1 ) ] ;
173+ }
174+
175+ const timedOut = { __proto__ : null } ;
176+ const outcome = await Promise . race ( [
177+ ( async ( ) => {
178+ for await ( const batch of merge ( readySource ( ) , pendingSource ( ) ) ) {
179+ assert . deepStrictEqual ( batch , [ Uint8Array . of ( 1 ) ] ) ;
180+ break ;
181+ }
182+ return true ;
183+ } ) ( ) ,
184+ new Promise ( ( resolve ) => setImmediate ( resolve , timedOut ) ) ,
185+ ] ) ;
186+
187+ assert . strictEqual ( outcome , true ) ;
188+ }
189+
190+ async function testMergeNaNAbortDoesNotAwaitCleanup ( ) {
191+ const ac = new AbortController ( ) ;
192+ const iterator = merge ( pendingSource ( ) , pendingSource ( ) , {
193+ __proto__ : null ,
194+ signal : ac . signal ,
195+ } ) [ Symbol . asyncIterator ] ( ) ;
196+ const next = iterator . next ( ) ;
197+ await new Promise ( setImmediate ) ;
198+ ac . abort ( NaN ) ;
199+
200+ const timedOut = { __proto__ : null } ;
201+ const outcome = await Promise . race ( [
202+ next . then (
203+ ( ) => ( { __proto__ : null , status : 'fulfilled' } ) ,
204+ ( error ) => ( { __proto__ : null , status : 'rejected' , error } ) ,
205+ ) ,
206+ new Promise ( ( resolve ) => setImmediate ( resolve , timedOut ) ) ,
207+ ] ) ;
208+
209+ assert . notStrictEqual ( outcome , timedOut ) ;
210+ assert . strictEqual ( outcome . status , 'rejected' ) ;
211+ assert . strictEqual ( Object . is ( outcome . error , NaN ) , true ) ;
212+ }
213+
111214async function testMergeConsumerBreak ( ) {
112215 let source1Return = false ;
113216 let source2Return = false ;
@@ -296,9 +399,8 @@ async function testMergeCleanupErrorOnly() {
296399 ) ;
297400}
298401
299- // Primary error + cleanup error: a source throws during iteration AND
300- // iterator.return() also throws. Should get a SuppressedError.
301- async function testMergePrimaryAndCleanupError ( ) {
402+ // A primary source error must not wait for asynchronous cleanup failures.
403+ async function testMergePrimaryErrorPrecedesCleanupError ( ) {
302404 async function * badSource ( ) {
303405 yield [ new TextEncoder ( ) . encode ( 'x' ) ] ;
304406 throw new Error ( 'primary boom' ) ;
@@ -319,15 +421,7 @@ async function testMergePrimaryAndCleanupError() {
319421 // Consume until error
320422 }
321423 } ,
322- ( err ) => {
323- assert . ok (
324- err instanceof SuppressedError ,
325- `Expected SuppressedError, got ${ err . constructor . name } ` ,
326- ) ;
327- assert . strictEqual ( err . error . message , 'primary boom' ) ;
328- assert . strictEqual ( err . suppressed . message , 'cleanup boom' ) ;
329- return true ;
330- } ,
424+ { message : 'primary boom' } ,
331425 ) ;
332426}
333427
@@ -360,6 +454,10 @@ Promise.all([
360454 testMergeWithAbortSignal ( ) ,
361455 testMergeSyncSources ( ) ,
362456 testMergeSourceError ( ) ,
457+ testMergeFalsySourceErrors ( ) ,
458+ testMergeSourceErrorDoesNotAwaitCleanup ( ) ,
459+ testMergeBreakDoesNotAwaitCleanup ( ) ,
460+ testMergeNaNAbortDoesNotAwaitCleanup ( ) ,
363461 testMergeConsumerBreak ( ) ,
364462 testMergeSignalMidIteration ( ) ,
365463 testMergeSignalDuringPendingMultiSourceRead ( ) ,
@@ -368,6 +466,6 @@ Promise.all([
368466 testMergeStringSources ( ) ,
369467 testMergeObjectLikeSources ( ) ,
370468 testMergeCleanupErrorOnly ( ) ,
371- testMergePrimaryAndCleanupError ( ) ,
469+ testMergePrimaryErrorPrecedesCleanupError ( ) ,
372470 testMergeBreakWithCleanupError ( ) ,
373471] ) . then ( common . mustCall ( ) ) ;
0 commit comments