UNPKG

@hyperlane-xyz/utils

Version:

General utilities and types for the Hyperlane network

140 lines 4.78 kB
import { rootLogger } from './logging.js'; import { assert } from './validation.js'; /** * Return a promise that resolves in ms milliseconds. * @param ms Time to wait */ export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Wait up to a given amount of time, and throw an error if the promise does not resolve in time. * @param promise The promise to timeout on. * @param timeoutMs How long to wait for the promise in milliseconds. * @param message The error message if a timeout occurs. */ export function timeout(promise, timeoutMs, message = 'Timeout reached') { if (!timeoutMs || timeoutMs <= 0) return promise; return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error(message)); }, timeoutMs); promise.then(resolve).catch(reject); }); } /** * Run a callback with a timeout. * @param timeoutMs How long to wait for the promise in milliseconds. * @param callback The callback to run. * @returns callback return value * @throws Error if the timeout is reached before the callback completes */ export async function runWithTimeout(timeoutMs, callback) { let timeoutId; const timeoutProm = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Timed out in ${timeoutMs}ms.`)); }, timeoutMs); }); try { const result = await Promise.race([callback(), timeoutProm]); return result; } finally { // @ts-ignore timeout gets set immediately by the promise constructor clearTimeout(timeoutId); } } /** * Executes a fetch request that fails after a timeout via an AbortController. * @param resource resource to fetch (e.g URL) * @param options fetch call options object * @param timeout timeout MS (default 10_000) * @returns fetch response */ export async function fetchWithTimeout(resource, options, timeout = 10000) { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const response = await fetch(resource, { ...options, signal: controller.signal, }); clearTimeout(id); return response; } /** * Retries an async function if it raises an exception, * using exponential backoff. * @param runner callback to run * @param attempts max number of attempts * @param baseRetryMs base delay between attempts * @returns runner return value */ export async function retryAsync(runner, attempts = 5, baseRetryMs = 50) { let saveError; for (let i = 0; i < attempts; i++) { try { const result = await runner(); return result; } catch (error) { saveError = error; await sleep(baseRetryMs * 2 ** i); } } throw saveError; } /** * Run a callback with a timeout, and retry if the callback throws an error. * @param runner callback to run * @param delayMs base delay between attempts * @param maxAttempts maximum number of attempts * @returns runner return value */ export async function pollAsync(runner, delayMs = 500, maxAttempts = undefined) { let attempts = 0; let saveError; while (!maxAttempts || attempts < maxAttempts) { try { const ret = await runner(); return ret; } catch (error) { rootLogger.debug(`Error in pollAsync`, { error }); saveError = error; attempts += 1; await sleep(delayMs); } } throw saveError; } /** * An enhanced Promise.race that returns * objects with the promise itself and index * instead of just the resolved value. */ export async function raceWithContext(promises) { const promisesWithContext = promises.map((p, i) => p.then((resolved) => ({ resolved, promise: p, index: i }))); return Promise.race(promisesWithContext); } /** * Map an async function over a list xs with a given concurrency level * Forked from https://github.com/celo-org/developer-tooling/blob/0c61e7e02c741fe10ecd1d733a33692d324cdc82/packages/sdk/base/src/async.ts#L128 * * @param concurrency number of `mapFn` concurrent executions * @param xs list of value * @param mapFn mapping function */ export async function concurrentMap(concurrency, xs, mapFn) { let res = []; assert(concurrency > 0, 'concurrency must be greater than 0'); for (let i = 0; i < xs.length; i += concurrency) { const remaining = xs.length - i; const sliceSize = Math.min(remaining, concurrency); const slice = xs.slice(i, i + sliceSize); res = res.concat(await Promise.all(slice.map((elem, index) => mapFn(elem, i + index)))); } return res; } //# sourceMappingURL=async.js.map