@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
149 lines (147 loc) • 4.34 kB
JavaScript
;
// 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 };
exports.asyncUtils = asyncUtils;
exports.backoffDelay = backoffDelay;
exports.concurrencyLimitedQueue = concurrencyLimitedQueue;
exports.memoizeAsync = memoizeAsync;
exports.pMapLimit = pMapLimit;
exports.pMapSeries = pMapSeries;
exports.pQueue = pQueue;
exports.promisePool = promisePool;
exports.promiseRace = promiseRace;
exports.sleep = sleep;
exports.timeoutPromise = timeoutPromise;
exports.withRetry = withRetry;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map