/** A request that can be given up: cancel() stops it (closing its connection, say). */ export interface Request { done: Promise cancel(): void } export interface Hedged { value: T /** The first request took longer than afterMs. */ late: boolean /** A second request was started. */ hedged: boolean /** The second request answered first. */ secondWon: boolean } /** * A hedged request: the first one not answered within afterMs is asked again * (again() may decline with null: no connection free), and the first answer * wins; the other request is cancelled. A failure only counts once both failed. */ export async function hedge(start: () => Request, again: () => Request | null, afterMs: number): Promise> { const first = start() let timer: NodeJS.Timeout | undefined const late = await Promise.race([ first.done.then( () => false, () => false ), new Promise((resolve) => (timer = setTimeout(() => resolve(true), afterMs))), ]) clearTimeout(timer) if (!late) return { value: await first.done, late, hedged: false, secondWon: false } const second = again() if (!second) return { value: await first.done, late, hedged: false, secondWon: false } const settled = new Set>() for (const r of [first, second]) { r.done.then( () => settled.add(r), () => settled.add(r) ) } try { const winner = await Promise.any([first, second].map((r) => r.done.then((value) => ({ r, value })))) const loser = winner.r === first ? second : first if (!settled.has(loser)) loser.cancel() return { value: winner.value, late, hedged: true, secondWon: winner.r === second } } catch (error) { throw error instanceof AggregateError ? error.errors[0] : error } }