UNPKG

@upendra.manike/async-utils

Version:

Async control utilities for JavaScript/TypeScript: retry with exponential backoff, concurrency-limited queues, promise pools, sequential execution, timeout handling, abort signals. Perfect for API calls, data fetching, and async operations. AI-friendly an

136 lines (135 loc) 4.07 kB
// src/index.ts function sleep(ms, signal) { return new Promise((resolve, reject) => { const t = setTimeout(() => { cleanup(); resolve(); }, ms); const onAbort = () => { cleanup(); reject(new DOMException("Aborted", "AbortError")); }; const cleanup = () => { clearTimeout(t); signal?.removeEventListener("abort", onAbort); }; if (signal) { if (signal.aborted) return onAbort(); signal.addEventListener("abort", onAbort); } }); } function backoffDelay(attempt, base = 250, kind = "exponential", jitter = true) { let delay = base; if (kind === "linear") delay = base * attempt; if (kind === "exponential") delay = base * Math.pow(2, attempt - 1); if (jitter) delay = Math.random() * delay; return Math.min(delay, 3e4); } async function withRetry(fn, opts = {}) { const { retries = 3, baseDelayMs = 250, backoff = "exponential", retryable = () => true, signal } = opts; let attempt = 0; while (true) { attempt += 1; try { if (signal?.aborted) throw new DOMException("Aborted", "AbortError"); return await fn(); } catch (err) { if (attempt > retries || !retryable(err)) throw err; const delay = backoffDelay(attempt, baseDelayMs, backoff); await sleep(delay, signal); } } } async function pMapSeries(items, mapper) { const out = []; for (let i = 0; i < items.length; i += 1) { out.push(await mapper(items[i], i)); } return out; } function timeoutPromise(promise, ms, message = "Timed out") { let timer; const timeout = new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(message)), ms); }); return Promise.race([promise, timeout]).finally(() => clearTimeout(timer)); } async function pQueue(tasks) { const results = []; for (const task of tasks) { results.push(await task()); } return results; } async function pMapLimit(items, limit, mapper) { if (limit <= 0) return []; const results = new Array(items.length); let nextIndex = 0; const workers = new Array(Math.min(limit, items.length)).fill(0).map(async () => { while (true) { const current = nextIndex; if (current >= items.length) break; nextIndex += 1; results[current] = await mapper(items[current], current); } }); await Promise.all(workers); return results; } function memoizeAsync(fn, keyFn) { const cache = /* @__PURE__ */ new Map(); return (...args) => { const key = keyFn ? keyFn(...args) : JSON.stringify(args); let cached = cache.get(key); if (!cached) { cached = fn(...args); cache.set(key, cached); } return cached; }; } function promiseRace(promises) { return Promise.race(promises); } async function concurrencyLimitedQueue(tasks, concurrency) { if (concurrency <= 0) return []; const results = new Array(tasks.length); let next = 0; let running = 0; let resolveAll = null; new Promise((resolve) => resolveAll = resolve); return new Promise((resolve, reject) => { const runNext = () => { if (next >= tasks.length && running === 0) { resolveAll(); return resolve(results); } while (running < concurrency && next < tasks.length) { const current = next++; running += 1; tasks[current]().then((val) => { results[current] = val; }).catch(reject).finally(() => { running -= 1; runNext(); }); } }; runNext(); }); } async function promisePool(iterator, concurrency) { const tasks = Array.from(iterator); return concurrencyLimitedQueue(tasks, concurrency); } var asyncUtils = { sleep, backoffDelay, withRetry, pMapSeries, timeoutPromise, pQueue, pMapLimit, memoizeAsync, promiseRace, concurrencyLimitedQueue, promisePool }; export { asyncUtils, backoffDelay, concurrencyLimitedQueue, memoizeAsync, pMapLimit, pMapSeries, pQueue, promisePool, promiseRace, sleep, timeoutPromise, withRetry }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map