@@ -16,6 +16,10 @@ const {
1616 DataViewPrototypeGetByteOffset,
1717 FunctionPrototypeCall,
1818 PromisePrototypeThen,
19+ PromiseResolve,
20+ PromiseWithResolvers,
21+ SafePromiseRace,
22+ Symbol,
1923 SymbolAsyncIterator,
2024 SymbolIterator,
2125 TypedArrayPrototypeGetBuffer,
@@ -29,8 +33,10 @@ const { markPromiseAsHandled } = internalBinding('util');
2933const {
3034 codes : {
3135 ERR_INVALID_ARG_TYPE ,
36+ ERR_INVALID_RETURN_VALUE ,
3237 } ,
3338} = require ( 'internal/errors' ) ;
39+ const { lazyDOMException } = require ( 'internal/util' ) ;
3440
3541const {
3642 isAnyArrayBuffer,
@@ -54,6 +60,81 @@ const {
5460// Bounds peak memory when arrays flow through transforms, which must
5561// allocate output for the entire batch at once.
5662const FROM_BATCH_SIZE = 128 ;
63+ const kNormalizationCancelled = Symbol ( 'kNormalizationCancelled' ) ;
64+
65+ function createNormalizationContext ( ) {
66+ return {
67+ __proto__ : null ,
68+ cancelled : false ,
69+ reason : undefined ,
70+ resolve : null ,
71+ suppressCleanup : false ,
72+ } ;
73+ }
74+
75+ function cancelNormalization ( context , reason , suppressCleanup = false ) {
76+ if ( context . cancelled ) return ;
77+ context . cancelled = true ;
78+ context . reason = reason ;
79+ context . suppressCleanup = suppressCleanup ;
80+ context . resolve ?. ( kNormalizationCancelled ) ;
81+ }
82+
83+ function throwIfNormalizationCancelled ( context ) {
84+ if ( context ?. cancelled ) throw context . reason ;
85+ }
86+
87+ async function waitForNormalization ( value , context ) {
88+ if ( context === undefined ) return value ;
89+ const { promise, resolve } = PromiseWithResolvers ( ) ;
90+ if ( context . cancelled ) {
91+ resolve ( kNormalizationCancelled ) ;
92+ } else {
93+ context . resolve = resolve ;
94+ }
95+ try {
96+ const result = await SafePromiseRace ( [
97+ PromiseResolve ( value ) ,
98+ promise ,
99+ ] ) ;
100+ throwIfNormalizationCancelled ( context ) ;
101+ return result ;
102+ } finally {
103+ if ( context . resolve === resolve ) context . resolve = null ;
104+ }
105+ }
106+
107+ function createNormalizationIterator ( createIterator ) {
108+ const context = createNormalizationContext ( ) ;
109+ const iterator = createIterator ( context ) ;
110+ return {
111+ __proto__ : null ,
112+ next ( value ) {
113+ return FunctionPrototypeCall ( iterator . next , iterator , value ) ;
114+ } ,
115+ return ( value ) {
116+ cancelNormalization (
117+ context , lazyDOMException ( 'Aborted' , 'AbortError' ) ) ;
118+ return FunctionPrototypeCall ( iterator . return , iterator , value ) ;
119+ } ,
120+ throw ( error ) {
121+ cancelNormalization ( context , error , true ) ;
122+ return FunctionPrototypeCall ( iterator . throw , iterator , error ) ;
123+ } ,
124+ [ SymbolAsyncIterator ] ( ) {
125+ return this ;
126+ } ,
127+ } ;
128+ }
129+
130+ function createNormalizationSource ( createIterator ) {
131+ return {
132+ __proto__ : null ,
133+ [ SymbolAsyncIterator ] ( ) {
134+ return createNormalizationIterator ( createIterator ) ;
135+ } ,
136+ } ;
137+ }
57138
58139// =============================================================================
59140// Type Guards and Detection
@@ -256,6 +337,101 @@ function* normalizeSyncSource(source) {
256337 }
257338}
258339
340+ function yieldNormalizationAbortable ( source , context ) {
341+ if ( context === undefined ) return source ;
342+ return {
343+ __proto__ : null ,
344+ [ SymbolAsyncIterator ] ( ) {
345+ const iteratorMethod = source [ SymbolAsyncIterator ] ;
346+ const iterator = FunctionPrototypeCall ( iteratorMethod , source ) ;
347+ const nextMethod = iterator . next ;
348+ let completed = false ;
349+ let closed = false ;
350+ let reading = false ;
351+
352+ async function closeSource ( suppressError ) {
353+ if ( closed ) return ;
354+ closed = true ;
355+ completed = true ;
356+
357+ if ( suppressError ) {
358+ try {
359+ const returnMethod = iterator . return ;
360+ if ( typeof returnMethod === 'function' ) {
361+ const cleanup = PromisePrototypeThen (
362+ PromiseResolve ( ) ,
363+ ( ) => FunctionPrototypeCall ( returnMethod , iterator ) ) ;
364+ markPromiseAsHandled ( cleanup ) ;
365+ }
366+ } catch {
367+ // Cancellation has precedence over source cleanup errors.
368+ }
369+ return ;
370+ }
371+
372+ const returnMethod = iterator . return ;
373+ if ( typeof returnMethod === 'function' ) {
374+ const result = await FunctionPrototypeCall ( returnMethod , iterator ) ;
375+ if ( ( typeof result !== 'object' && typeof result !== 'function' ) ||
376+ result === null ) {
377+ throw new ERR_INVALID_RETURN_VALUE (
378+ 'an object' , 'iterator.return()' , result ) ;
379+ }
380+ }
381+ }
382+
383+ return {
384+ __proto__ : null ,
385+ async next ( ) {
386+ if ( completed ) {
387+ return { __proto__ : null , done : true , value : undefined } ;
388+ }
389+ throwIfNormalizationCancelled ( context ) ;
390+ reading = true ;
391+
392+ try {
393+ const next = FunctionPrototypeCall ( nextMethod , iterator ) ;
394+ const result = await waitForNormalization ( next , context ) ;
395+ if ( ( typeof result !== 'object' && typeof result !== 'function' ) ||
396+ result === null ) {
397+ throw new ERR_INVALID_RETURN_VALUE (
398+ 'an object' , 'iterator.next()' , result ) ;
399+ }
400+ if ( result . done ) {
401+ reading = false ;
402+ throwIfNormalizationCancelled ( context ) ;
403+ completed = true ;
404+ closed = true ;
405+ return { __proto__ : null , done : true , value : result . value } ;
406+ }
407+ const value = result . value ;
408+ reading = false ;
409+ throwIfNormalizationCancelled ( context ) ;
410+ return { __proto__ : null , done : false , value } ;
411+ } catch ( error ) {
412+ if ( context . cancelled ) await closeSource ( true ) ;
413+ reading = false ;
414+ throw error ;
415+ }
416+ } ,
417+ async return ( value ) {
418+ await closeSource (
419+ context . suppressCleanup || ( context . cancelled && reading ) ) ;
420+ return { __proto__ : null , done : true , value } ;
421+ } ,
422+ async throw ( error ) {
423+ await closeSource (
424+ context . suppressCleanup || ( context . cancelled && reading ) ) ;
425+ throw error ;
426+ } ,
427+ [ SymbolAsyncIterator ] ( ) {
428+ return this ;
429+ } ,
430+ } ;
431+ } ,
432+ } ;
433+ }
434+
259435// =============================================================================
260436// Async Normalization (for from and async contexts)
261437// =============================================================================
@@ -266,11 +442,15 @@ function* normalizeSyncSource(source) {
266442 * and protocol conversions.
267443 * @yields {Uint8Array}
268444 */
269- async function * normalizeAsyncValue ( value , allowNestedAsyncStreamables = true ) {
445+ async function * normalizeAsyncValue (
446+ value , allowNestedAsyncStreamables = true , context ) {
447+ throwIfNormalizationCancelled ( context ) ;
448+
270449 // Handle promises first
271450 if ( isPromise ( value ) ) {
272- const resolved = await value ;
273- yield * normalizeAsyncValue ( resolved , allowNestedAsyncStreamables ) ;
451+ const resolved = await waitForNormalization ( value , context ) ;
452+ yield * normalizeAsyncValue (
453+ resolved , allowNestedAsyncStreamables , context ) ;
274454 return ;
275455 }
276456
@@ -293,41 +473,47 @@ async function* normalizeAsyncValue(value, allowNestedAsyncStreamables = true) {
293473 if ( hasProtocol ( value , toAsyncStreamable ) ) {
294474 const result = FunctionPrototypeCall ( value [ toAsyncStreamable ] , value ) ;
295475 if ( isPromise ( result ) ) {
296- yield * normalizeAsyncValue ( await result , allowNestedAsyncStreamables ) ;
476+ yield * normalizeAsyncValue (
477+ await waitForNormalization ( result , context ) ,
478+ allowNestedAsyncStreamables ,
479+ context ) ;
297480 } else {
298- yield * normalizeAsyncValue ( result , allowNestedAsyncStreamables ) ;
481+ yield * normalizeAsyncValue (
482+ result , allowNestedAsyncStreamables , context ) ;
299483 }
300484 return ;
301485 }
302486
303487 // Handle ToStreamable protocol
304488 if ( hasProtocol ( value , toStreamable ) ) {
305489 const result = FunctionPrototypeCall ( value [ toStreamable ] , value ) ;
306- yield * normalizeAsyncValue ( result , allowNestedAsyncStreamables ) ;
490+ yield * normalizeAsyncValue ( result , allowNestedAsyncStreamables , context ) ;
307491 return ;
308492 }
309493
310494 // Handle arrays (which are also iterable, but check first for efficiency)
311495 if ( ArrayIsArray ( value ) ) {
312496 for ( let i = 0 ; i < value . length ; i ++ ) {
313- yield * normalizeAsyncValue ( value [ i ] , allowNestedAsyncStreamables ) ;
497+ yield * normalizeAsyncValue (
498+ value [ i ] , allowNestedAsyncStreamables , context ) ;
314499 }
315500 return ;
316501 }
317502
318503 // Handle async iterables (check before sync iterables since some objects
319504 // have both)
320505 if ( isAsyncIterable ( value ) ) {
321- for await ( const item of value ) {
322- yield * normalizeAsyncValue ( item , allowNestedAsyncStreamables ) ;
506+ const iterable = yieldNormalizationAbortable ( value , context ) ;
507+ for await ( const item of iterable ) {
508+ yield * normalizeAsyncValue ( item , allowNestedAsyncStreamables , context ) ;
323509 }
324510 return ;
325511 }
326512
327513 // Handle sync iterables
328514 if ( isSyncIterable ( value ) ) {
329515 for ( const item of value ) {
330- yield * normalizeAsyncValue ( item , allowNestedAsyncStreamables ) ;
516+ yield * normalizeAsyncValue ( item , allowNestedAsyncStreamables , context ) ;
331517 }
332518 return ;
333519 }
@@ -346,10 +532,13 @@ async function* normalizeAsyncValue(value, allowNestedAsyncStreamables = true) {
346532 * @param {AsyncIterable|Iterable } source
347533 * @yields {Uint8Array[]}
348534 */
349- async function * normalizeAsyncSource ( source ) {
535+ async function * normalizeAsyncSource ( source , context ) {
536+ throwIfNormalizationCancelled ( context ) ;
537+
350538 // Prefer async iteration if available
351539 if ( isAsyncIterable ( source ) ) {
352- for await ( const value of source ) {
540+ const iterable = yieldNormalizationAbortable ( source , context ) ;
541+ for await ( const value of iterable ) {
353542 // Fast path 1: value is already a Uint8Array[] batch
354543 if ( isUint8ArrayBatch ( value ) ) {
355544 if ( value . length > 0 ) {
@@ -364,7 +553,7 @@ async function* normalizeAsyncSource(source) {
364553 }
365554 // Slow path: normalize the value
366555 let batch = [ ] ;
367- for await ( const chunk of normalizeAsyncValue ( value ) ) {
556+ for await ( const chunk of normalizeAsyncValue ( value , true , context ) ) {
368557 ArrayPrototypePush ( batch , chunk ) ;
369558 if ( batch . length === FROM_BATCH_SIZE ) {
370559 yield batch ;
@@ -383,6 +572,7 @@ async function* normalizeAsyncSource(source) {
383572 let batch = [ ] ;
384573
385574 for ( const value of source ) {
575+ throwIfNormalizationCancelled ( context ) ;
386576 // Fast path 1: value is already a Uint8Array[] batch
387577 if ( isUint8ArrayBatch ( value ) ) {
388578 // Flush any accumulated batch first
@@ -408,7 +598,7 @@ async function* normalizeAsyncSource(source) {
408598 batch = [ ] ;
409599 }
410600 let asyncBatch = [ ] ;
411- for await ( const chunk of normalizeAsyncValue ( value , false ) ) {
601+ for await ( const chunk of normalizeAsyncValue ( value , false , context ) ) {
412602 ArrayPrototypePush ( asyncBatch , chunk ) ;
413603 if ( asyncBatch . length === FROM_BATCH_SIZE ) {
414604 yield asyncBatch ;
@@ -434,6 +624,12 @@ async function* normalizeAsyncSource(source) {
434624 ) ;
435625}
436626
627+ async function * normalizeAsyncStreamableResult ( result , context ) {
628+ const resolved = await waitForNormalization ( result , context ) ;
629+ const source = resolved ?. [ kValidatedSource ] ? resolved : from ( resolved ) ;
630+ yield * yieldNormalizationAbortable ( source , context ) ;
631+ }
632+
437633// =============================================================================
438634// Public API: from() and fromSync()
439635// =============================================================================
@@ -610,19 +806,8 @@ function from(input) {
610806 if ( result ?. [ kValidatedSource ] ) {
611807 return result ;
612808 }
613- return {
614- __proto__ : null ,
615- async * [ SymbolAsyncIterator ] ( ) {
616- // The result may be a Promise. Check validated on both the Promise
617- // itself (if tagged) and the resolved value.
618- const resolved = await result ;
619- if ( resolved ?. [ kValidatedSource ] ) {
620- yield * resolved [ SymbolAsyncIterator ] ( ) ;
621- return ;
622- }
623- yield * from ( resolved ) [ SymbolAsyncIterator ] ( ) ;
624- } ,
625- } ;
809+ return createNormalizationSource (
810+ ( context ) => normalizeAsyncStreamableResult ( result , context ) ) ;
626811 }
627812
628813 // Check toStreamable protocol (takes precedence over iteration protocols)
@@ -640,7 +825,8 @@ function from(input) {
640825 ) ;
641826 }
642827
643- return normalizeAsyncSource ( input ) ;
828+ return createNormalizationIterator (
829+ ( context ) => normalizeAsyncSource ( input , context ) ) ;
644830}
645831
646832// =============================================================================
0 commit comments