interface AsyncIterable { [Symbol.asyncIterator](): AsyncIterator; } type Channel = | AsyncIterable | (() => AsyncIterable); type Channels = | readonly [] | readonly [Channel, ...Channel[]] | readonly Channel[] | Record; type ChannelIteratorResult = C extends () => AsyncIterable ? IteratorResult : C extends AsyncIterable ? IteratorResult : never; type Selection = T extends readonly unknown[] ? number extends T['length'] ? { readonly name: string; readonly result: T[number] extends Channel ? ChannelIteratorResult : never; } : { [P in keyof T]: T[P] extends Channel ? { readonly name: P; readonly result: ChannelIteratorResult; } : never; }[number] : { [P in keyof T]: T[P] extends Channel ? { readonly name: P; readonly result: ChannelIteratorResult; } : never; }[keyof T]; export async function* select( channels: T, ): AsyncGenerator, undefined, undefined> { const reqs = new Set(Object.entries(channels) .map(([name, chan]) => ( chan = typeof chan === 'function' ? chan() : chan, take(name, chan[Symbol.asyncIterator]())))); while (reqs.size > 0) { const { 0: req, 1: name, 2: chan, 3: result } = await Promise.race(reqs); assert(reqs.has(req)); reqs.delete(req); !result.done && reqs.add(take(name, chan)); yield { name, result } as Selection; } } type Request = Promise, IteratorResult]>; function take(name: string, chan: AsyncIterator): Request { const req: Request = chan.next().then(result => [req, name, chan, result]); return req; }