@@ -2,12 +2,6 @@ import { promiseWithResolvers } from './promise.ts';
22
33const signalCache = new WeakMap < AbortSignal , Promise < void > > ( ) ;
44
5- type Fallback < T , F > = T extends undefined ? F : T ;
6-
7- type Entries < T > = {
8- [ K in keyof T ] : [ K , T [ K ] ] ;
9- } [ keyof T ] [ ] ;
10-
115/**
126 * Channel is borrowed from Go.
137 *
@@ -38,8 +32,7 @@ export type Channel<T> = {
3832 *
3933 * This is as {@link ReadChannel.wait}, but with a wider type.
4034 */
41- wait < V = Channel < T > > ( ) : Promise < Fallback < V , Channel < T > > > ;
42- wait < V = Channel < T > > ( value : V ) : Promise < Fallback < V , Channel < T > > > ;
35+ wait < V = Channel < T > > ( value ?: V ) : Promise < V > ;
4336} & ReadChannel < T > ;
4437
4538export type ReadChannel < T > = {
@@ -56,137 +49,130 @@ export type ReadChannel<T> = {
5649 * Repeated calls to `wait()` will delay a microtask before resolving.
5750 * This allows a resolved task to safely consume a value synchronously after this resolves.
5851 *
59- * For no other reason than convenience, this resolves with itself.
60- * This isn't the value itself, the assumption is you can then synchronously consume the next value with {@link Channel #next}.
52+ * For no other reason than convenience, this resolves with itself, unless you include another value as an argument .
53+ * The assumption is you can then synchronously consume the next value with {@link ReadChannel #next}.
6154 */
62- wait < V = ReadChannel < T > > ( ) : Promise < Fallback < V , ReadChannel < T > > > ;
63- wait < V = ReadChannel < T > > ( value : V ) : Promise < Fallback < V , ReadChannel < T > > > ;
55+ wait < V = ReadChannel < T > > ( value ?: V ) : Promise < V > ;
6456
6557 /**
66- * Returns whether {@link Channel #next} has an available value.
58+ * Returns whether {@link ReadChannel #next} has an available value.
6759 */
6860 pending ( ) : boolean ;
6961
7062 /**
71- * Consumes a value from this {@link Channel }, if possible.
63+ * Consumes a value from this {@link ReadChannel }, if possible.
7264 * Otherwise, returns `undefined`.
7365 */
7466 next ( ) : T | undefined ;
7567} ;
7668
7769type MessageType < Q > = Q extends ReadChannel < infer X > ? X : never ;
7870
79- type SelectType < Q > = Q extends SelectRequest < infer X > ? X : never ;
80-
81- export type SelectRequest < T > = { [ key : string | symbol ] : ReadChannel < T > } ;
71+ export type SelectRequest = { [ key : string | symbol ] : ReadChannel < any > | undefined } ;
8272
83- export type SelectResult < T extends SelectRequest < V > , V = SelectType < T > > = {
84- [ TKey in keyof T ] : Readonly < {
73+ export type SelectResult < TChannels extends SelectRequest > = {
74+ [ TKey in keyof TChannels ] : Readonly < {
8575 key : TKey ;
86- ch : NonNullable < T [ TKey ] > ;
87- m : MessageType < T [ TKey ] > ;
76+ ch : NonNullable < TChannels [ TKey ] > ;
77+ m : MessageType < TChannels [ TKey ] > ;
8878 closed : boolean ;
8979 } > ;
90- } [ keyof T ] ;
91-
92- export type SelectOption < T extends SelectRequest < V > , V = SelectType < T > > = {
93- [ TKey in keyof T ] : Readonly < {
94- key : TKey ;
95- ch : NonNullable < T [ TKey ] > ;
96- } > ;
97- } [ keyof T ] ;
80+ } [ keyof TChannels ] ;
9881
9982/**
10083 * Waits for the first {@link Channel} that is ready based on key.
10184 * Returns with the key for matching.
10285 *
103- * This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
86+ * For safety, throws if no valid channels are passed (zero channels or all `undefined`).
87+ *
88+ * This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
10489 */
105- export function select < T extends SelectRequest < V > , V = SelectType < T > > (
106- o : T ,
107- ) : Promise < SelectResult < T , V > > ;
90+ export function select < TChannels extends SelectRequest > (
91+ o : TChannels ,
92+ ) : Promise < SelectResult < TChannels > > ;
10893
10994/**
11095 * Waits for the first {@link Channel} that is ready based on key.
11196 * Returns with the key for matching.
11297 * Prefers the passed {@link AbortSignal}, which if aborted, returns `undefined`.
11398 *
114- * This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
99+ * Does not throw if missing channels, as the {@link AbortSignal} is an implied 'channel'.
100+ *
101+ * This uses JS' default object ordering for what is "first" when many are ready: integers >= 0 in order, all others, symbols.
115102 */
116- export function select < T extends SelectRequest < V > , V = SelectType < T > > (
117- o : T ,
103+ export function select < TChannels extends SelectRequest > (
104+ o : TChannels ,
118105 signal : AbortSignal ,
119- ) : Promise < SelectResult < T , V > | undefined > ;
106+ ) : Promise < SelectResult < TChannels > | undefined > ;
107+
108+ export function select < TChannels extends SelectRequest > (
109+ o : TChannels ,
110+ ) : Promise < SelectResult < TChannels > > ;
120111
121- export function select < T extends SelectRequest < V > , V = SelectType < T > > (
122- o : T ,
112+ export function select < TChannels extends SelectRequest > (
113+ o : TChannels ,
123114 signal ?: AbortSignal ,
124- ) : Promise < SelectResult < T , V > | undefined > {
115+ ) : Promise < SelectResult < TChannels > | undefined > {
125116 if ( signal ?. aborted ) {
126117 return Promise . resolve ( ) . then ( ( ) => undefined ) ;
127118 }
128119
129- const sync = selectDefault < T , V > ( o ) ;
120+ const sync = selectDefault ( o ) ;
130121 if ( sync !== undefined ) {
131122 // nb. load-bearing extra Promise.resolve()
132123 return Promise . resolve ( ) . then ( ( ) => sync ) ;
133124 }
134125
135126 // basically the key type of SelectResult
136- const options : Promise < SelectOption < T , V > | undefined > [ ] = [ ] ;
127+ const options : Promise < void | { key : any ; ch : ReadChannel < any > ; m : any ; closed : boolean } > [ ] = [ ] ;
137128
129+ let signalPromise : Promise < void > | undefined ;
138130 if ( signal !== undefined ) {
139- let signalPromise : Promise < void > | undefined = signalCache . get ( signal ) ;
131+ signalPromise = signalCache . get ( signal ) ;
140132 if ( signalPromise === undefined ) {
141133 signalPromise = new Promise ( ( r ) =>
142134 signal . addEventListener ( 'abort' , ( ) => r ( ) , { once : true } ) ,
143135 ) ;
144136 signalCache . set ( signal , signalPromise ) ;
145137 }
146- options . push ( signalPromise . then ( ( ) => undefined ) ) ;
138+ options . push ( signalPromise ) ;
147139 }
148140
149- // Assertion is needed to provide object entry key value pairs
150- const entries = Object . entries ( o ) as Entries < T > ;
151- entries . forEach ( ( [ key , ch ] ) => {
141+ Object . entries ( o ) . forEach ( ( [ key , ch ] ) => {
152142 if ( ch ) {
153- options . push ( ch . wait ( { key, ch } ) ) ;
143+ options . push ( ch . wait ( { key, ch, m : undefined , closed : false } ) ) ;
154144 }
155145 } ) ;
146+ if ( ! options . length ) {
147+ throw new Error ( `select() without any valid channels` ) ;
148+ }
149+
156150 const out = Promise . race ( options )
157- . then ( ( choice ) : SelectResult < T , V > | undefined => {
151+ . then ( ( choice ) => {
158152 if ( choice ) {
159- const { key, ch } = choice ;
160- return {
161- key,
162- ch,
163- // wait() resolving implies that ch.next() will not return undefined
164- m : choice . ch . next ( ) as MessageType < T [ keyof T ] > ,
165- closed : choice . ch . closed ,
166- } ;
153+ choice . m = choice . ch . next ( ) ;
154+ choice . closed = choice . ch . closed ;
167155 }
168156 return choice ;
169157 } )
170158 // nb. load-bearing
171159 . then ( ( x ) => x ) ;
172- return out ;
160+ return out as any ;
173161}
174162
175163/**
176164 * Selects the first {@link ReadChannel} that is ready, or `undefined` if none are ready.
177165 * Returns with the key for matching.
178166 *
179- * This uses JS' default object ordering: integers >= 0 in order, all others, symbols.
167+ * This uses JS' default object ordering for what is "first" when many are ready : integers >= 0 in order, all others, symbols.
180168 */
181- export function selectDefault < T extends SelectRequest < V > , V = SelectType < T > > (
182- o : Partial < T > ,
183- ) : SelectResult < T , V > | undefined {
184- for ( const pendingKey of Reflect . ownKeys ( o ) ) {
185- // Assertion is needed to maintain generic type pairing
186- const [ key , ch ] = [ pendingKey , o [ pendingKey ] ] as Entries < T > [ number ] ;
169+ export function selectDefault <
170+ TChannels extends { [ key : string | symbol ] : ReadChannel < any > | undefined } ,
171+ > ( o : TChannels ) : SelectResult < TChannels > | undefined {
172+ for ( const key of Reflect . ownKeys ( o ) ) {
173+ const ch = o [ key ] ;
187174 if ( ch ?. pending ( ) ) {
188- // pending() returning true implies that ch.next() will not return undefined
189- return { key, ch, m : ch . next ( ) as MessageType < T [ keyof T ] > , closed : ch . closed } ;
175+ return { key, ch : ch as any , m : ch . next ( ) , closed : ch . closed } ;
190176 }
191177 }
192178 return undefined ;
@@ -217,7 +203,7 @@ class ChannelImpl<T> implements Channel<T> {
217203 private tail : ChannelItem < T > | null = null ;
218204
219205 private readonly waits : {
220- promise : Promise < unknown > ;
206+ promise : Promise < any > ;
221207 resolve : ( ) => void ;
222208 } [ ] = [ ] ;
223209
@@ -242,12 +228,10 @@ class ChannelImpl<T> implements Channel<T> {
242228 } ) ;
243229 }
244230
245- wait < V = Channel < T > > ( value ?: V ) : Promise < Fallback < V , Channel < T > > > {
246- const pr = promiseWithResolvers < Fallback < V , Channel < T > > > ( ) ;
247- this . waits . push ( {
248- promise : pr . promise ,
249- resolve : ( ) => pr . resolve ( ( value ?? this ) as Fallback < V , Channel < T > > ) ,
250- } ) ;
231+ // awkward typing to allow automatic resolution with any other value
232+ wait < V = Channel < T > > ( value : V = this as any ) : Promise < V > {
233+ const pr = promiseWithResolvers < V > ( ) ;
234+ this . waits . push ( { promise : pr . promise , resolve : ( ) => pr . resolve ( value ) } ) ;
251235
252236 this . kickoffResolveTask ( ) ;
253237
@@ -312,21 +296,11 @@ class ChannelImpl<T> implements Channel<T> {
312296/**
313297 * Converts a {@link AbortSignal} into a channel which is closed when it aborts.
314298 */
315- export function channelForSignal < T = AbortSignal > (
316- s : AbortSignal ,
317- ) : ReadChannel < Fallback < T , AbortSignal > > ;
318- export function channelForSignal < T = AbortSignal > (
319- s : AbortSignal ,
320- v : T ,
321- ) : ReadChannel < Fallback < T , AbortSignal > > ;
322- export function channelForSignal < T = AbortSignal > (
323- s : AbortSignal ,
324- v ?: T ,
325- ) : ReadChannel < Fallback < T , AbortSignal > > {
299+ export function channelForSignal < T = AbortSignal > ( s : AbortSignal , v : T = s as any ) : ReadChannel < T > {
326300 const o = {
327301 closed : false ,
328302
329- async wait < V = ReadChannel < T > > ( value : V = o ) : Promise < V > {
303+ async wait < V = ReadChannel < T > > ( value : V = o as any ) : Promise < V > {
330304 if ( s . aborted ) {
331305 return value ;
332306 }
@@ -337,10 +311,10 @@ export function channelForSignal<T = AbortSignal>(
337311 return s . aborted ;
338312 } ,
339313
340- next ( ) : Fallback < T , AbortSignal > | undefined {
314+ next ( ) : T | undefined {
341315 if ( s . aborted ) {
342316 o . closed = true ;
343- return ( v ?? s ) as Fallback < T , AbortSignal > ;
317+ return v ;
344318 }
345319 return undefined ;
346320 } ,
0 commit comments