From 6306a507e488dfbec6ea472104dddd14b43f59a4 Mon Sep 17 00:00:00 2001 From: philipahlberg Date: Tue, 4 Aug 2020 19:33:57 +0200 Subject: [PATCH] Add implementation --- src/index.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++++-- tests/index.mjs | 32 ++++++++++++++++++-- 2 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8d9b8a2..57afcee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,76 @@ -export function add(a: number, b: number): number { - return a + b; +/** + * Create an AsyncIterable that yields the values + * of the given Promises in the order they resolve. + * + * @param promises An iterable of Promises. + */ +export const createRaceIterable = (promises: Iterable>): AsyncIterable => { + return new RaceIterable(promises); +}; + +/** + * A simple wrapper to go from AsyncIterable to AsyncIterator. + */ +class RaceIterable implements AsyncIterable { + private iterable: Iterable>; + + constructor(iterable: Iterable>) { + this.iterable = iterable; + } + + [Symbol.asyncIterator](): AsyncIterator { + return new RaceIterator(this.iterable); + } } + +class RaceIterator implements AsyncIterator { + private promises: Map>; + + constructor(iterable: Iterable>) { + // Create a Map from key to Promise + const map = new Map>(); + // Assign a unique `key` to every Promise + for (const [key, promise] of enumerate(iterable)) { + // Create a wrapped Promise that resolves to [key, value] + const keyedPromise = createKeyedPromise(key, promise); + // Insert the wrapped Promise by its key + map.set(key, keyedPromise); + } + this.promises = map; + } + + async next(): Promise> { + // If the pool is empty, so is the iterator. + if (this.promises.size === 0) { + return { + done: true, + value: undefined, + }; + } + + // Wait for the first Promise to resolve. + // Note: `Promise.race` creates a *new* Promise; it is not + // actually the Promise that resolved first. + const [key, value] = await Promise.race(this.promises.values()); + // Remove the resolved Promise from the pool. + this.promises.delete(key); + // Resolve with the resolved value of the original Promise. + return { + done: false, + value, + }; + } +} + +function* enumerate(iterable: Iterable): Iterable<[number, T]> { + let i = 0; + for (const value of iterable) { + yield [i, value]; + i = i + 1; + } +} + +const createKeyedPromise = async (key: number, promise: Promise): Promise<[number, T]> => { + const value = await promise; + return [key, value]; +}; diff --git a/tests/index.mjs b/tests/index.mjs index 668c07a..2a21644 100644 --- a/tests/index.mjs +++ b/tests/index.mjs @@ -1,6 +1,32 @@ import { assertEqual } from '@windtunnel/assert'; -import { add } from '../dist/index.mjs'; +import { createRaceIterable } from '../dist/index.mjs'; -export function testAdd() { - assertEqual(add(1, 2), 3, '1 + 2 = 3'); +const timeout = (ms) => new Promise((resolve) => { + setTimeout(resolve, ms); +}); + +const collect = async (iterable) => { + const values = []; + for await (const value of iterable) { + values.push(value); + } + return values; +}; + +export async function testRaceIterable() { + const d = timeout(400).then(() => 'd'); + const c = timeout(300).then(() => 'c'); + const b = timeout(200).then(() => 'b'); + const a = timeout(100).then(() => 'a'); + + const iterable = createRaceIterable([ + c, + a, + d, + b, + ]); + + const values = await collect(iterable); + + assertEqual(values, ['a', 'b', 'c', 'd'], 'should resolve in order from fastest to slowest'); }