22// Pure primitives — no external deps, no engine-specific imports.
33
44/**
5- * Async push/pull channel. No array buffering — uses linked promise pairs .
5+ * Async push/pull channel with unbounded buffer when push outpaces pull .
66 *
77 * **Error handling:** The channel itself never throws — it is a passive data
88 * structure. Producers call `push()` and `close()`; neither can fail.
1212export function channel < T > ( ) : AsyncIterable < T > & {
1313 push ( value : T ) : void
1414 close ( ) : void
15+ onReturn ?: ( ) => void | Promise < unknown >
1516} {
1617 let resolve : ( ( result : IteratorResult < T > ) => void ) | null = null
1718 let done = false
1819 const pending : T [ ] = [ ] // only used when push() is called before next()
20+ let onReturn : ( ( ) => void | Promise < unknown > ) | undefined
1921
2022 const iter : AsyncIterableIterator < T > = {
2123 [ Symbol . asyncIterator ] ( ) {
@@ -30,9 +32,20 @@ export function channel<T>(): AsyncIterable<T> & {
3032 resolve = r
3133 } )
3234 } ,
35+ async return ( ) {
36+ done = true
37+ pending . length = 0
38+ if ( resolve ) {
39+ const r = resolve
40+ resolve = null
41+ r ( { value : undefined as any , done : true } )
42+ }
43+ await onReturn ?.( )
44+ return { value : undefined as any , done : true }
45+ } ,
3346 }
3447
35- return Object . assign ( iter , {
48+ const result = Object . assign ( iter , {
3649 push ( value : T ) {
3750 if ( done ) return
3851 if ( resolve ) {
@@ -52,6 +65,16 @@ export function channel<T>(): AsyncIterable<T> & {
5265 }
5366 } ,
5467 } )
68+ Object . defineProperty ( result , 'onReturn' , {
69+ set ( fn : ( ( ) => void | Promise < unknown > ) | undefined ) {
70+ onReturn = fn
71+ } ,
72+ get ( ) {
73+ return onReturn
74+ } ,
75+ configurable : true ,
76+ } )
77+ return result as typeof result & { onReturn ?: ( ) => void | Promise < unknown > }
5578}
5679
5780/**
@@ -87,13 +110,19 @@ export async function* merge<T>(
87110 enqueue ( i )
88111 }
89112
90- while ( pending . size > 0 ) {
91- const { index, result } = await Promise . race ( pending . values ( ) )
92- if ( result . done ) {
93- pending . delete ( index )
94- } else {
95- yield result . value
96- enqueue ( index )
113+ try {
114+ while ( pending . size > 0 ) {
115+ const { index, result } = await Promise . race ( pending . values ( ) )
116+ if ( result . done ) {
117+ pending . delete ( index )
118+ } else {
119+ yield result . value
120+ enqueue ( index )
121+ }
122+ }
123+ } finally {
124+ for ( const it of iterators ) {
125+ it . return ?.( )
97126 }
98127 }
99128}
@@ -115,16 +144,29 @@ export function split<T, U extends T>(
115144 iterable : AsyncIterable < T > ,
116145 predicate : ( item : T ) => item is U
117146) : [ AsyncIterable < U > , AsyncIterable < Exclude < T , U > > ] {
147+ const sourceIterator = iterable [ Symbol . asyncIterator ] ( )
118148 const matches = channel < U > ( )
119149 const rest = channel < Exclude < T , U > > ( )
120150
151+ let aborted = false
152+ const abort = ( ) => {
153+ if ( aborted ) return
154+ aborted = true
155+ matches . close ( )
156+ rest . close ( )
157+ return sourceIterator . return ?.( )
158+ }
159+ matches . onReturn = abort
160+ rest . onReturn = abort
121161 ; ( async ( ) => {
122162 try {
123- for await ( const item of iterable ) {
124- if ( predicate ( item ) ) {
125- matches . push ( item )
163+ while ( true ) {
164+ const result = await sourceIterator . next ( )
165+ if ( result . done ) break
166+ if ( predicate ( result . value ) ) {
167+ matches . push ( result . value )
126168 } else {
127- rest . push ( item as Exclude < T , U > )
169+ rest . push ( result . value as Exclude < T , U > )
128170 }
129171 }
130172 } finally {
0 commit comments