asyncbox
A collection of ES7 async/await utilities. Install via NPM:
npm install asyncbox
Then, behold!
Sleep
An async/await version of setTimeout
import { sleep } from 'asyncbox';
async function myFn () {
await sleep(1000);
};
withTimeout
Race a promise against a deadline. If the promise settles first, its result is returned; if the deadline passes first, the call rejects. By default that rejection is a TimeoutError whose message includes how long you waited; you can pass a non-empty string for a custom TimeoutError message, or an Error instance to reject with that exact value. Omitted or other falsy third arguments (except a real Error) use the default timeout message.
withTimeout only stops waiting on the promise: the underlying async work keeps running (there is no built-in cancellation). Aborting a fetch, clearing timers, or tearing down other resources is the caller’s responsibility if you need that behavior.
import { withTimeout, TimeoutError } from 'asyncbox';
await withTimeout(fetch('/slow').then((r) => r.json()), 5000);
await withTimeout(slowWork(), 10_000, 'slowWork exceeded 10s');
const err = new MyAppError('upstream did not respond');
await withTimeout(slowWork(), 5000, err);
Cancelling an in-flight fetch when the timeout wins:
import { withTimeout, TimeoutError } from 'asyncbox';
async function fetchJsonWithTimeout(url, timeoutMs) {
const controller = new AbortController();
const body = fetch(url, { signal: controller.signal }).then((r) => r.json());
try {
return await withTimeout(body, timeoutMs);
} catch (err) {
if (err instanceof TimeoutError) {
controller.abort();
}
throw err;
}
}
Long Sleep
Sometimes Promise.delay or setTimeout are inaccurate for large wait times. To safely wait for these long times (e.g. in the 5+ minute range), you can use longSleep:
import { longSleep } from 'asyncbox';
async function myFn () {
await longSleep(10 * 60 * 1000);
await longSleep(5000, {thresholdMs: 10000});
await longSleep(5000, {intervalMs: 500});
}
You can also pass a progressCb option which is a callback function that receives an object with the properties elapsedMs, timeLeft, and progress. This will be called on every wait interval so you can do your wait logging or whatever.
function progressCb({elapsedMs, timeLeft, progress}) {
console.log(`We are {progress * 100}% complete waiting`);
}
await longSleep(10 * 60 * 1000, {progressCb});
Retry
An async/await way of running a method until it doesn't throw an error
import { sleep, retry } from 'asyncbox';
async function flakeyFunction (val1, val2) {
if (val1 < 10) {
throw new Error("this is a flakey value");
}
await sleep(1000);
return val1 + val2;
}
async function myFn () {
let randVals = [Math.random() * 100, Math.random() * 100];
let randSum = await retry(3, flakeyFunction, ...randVals);
}
You can also use retryInterval to add a sleep in between retries. This can be
useful if you want to throttle how fast we retry:
await retryInterval(3, 1500, expensiveFunction, ...args);
Filter/Map
Filter and map are pretty handy concepts, and now you can write filter and map
functions that execute asynchronously!
import { asyncmap, asyncfilter } from 'asyncbox';
Then in your async functions, you can do:
const items = [1, 2, 3, 4];
const slowSquare = async (n) => { await sleep(5); return n * n; };
let newItems = await asyncmap(items, slowSquare);
console.log(newItems);
const slowEven = async (n) => { await sleep(5); return n % 2 === 0; };
newItems = await asyncfilter(items, slowEven);
console.log(newItems);
By default, asyncmap and asyncfilter run their operations in parallel, but you
can set the third argument to false to enforce sequential execution, or set a custom
concurrency pool limit using {concurrency: <number>}:
const items = [1, 2, 3, 4];
const slowSquare = async (n) => { await sleep(5); return n * n; };
const newItemsSeq = await asyncmap(items, slowSquare, false);
const newItemsMaxTwo = await asyncmap(items, slowSquare, {concurrency: 2});
waitForCondition
Takes a condition (a function returning a boolean or boolean promise),
and waits until the condition is true.
Throws a /Condition unmet/ error if the condition has not been
satisfied within the allocated time, unless an error is provided in
the options, as the error property, which is either thrown itself, or
used as the message.
The condition result is returned if it is not falsy. If the condition
throws an error then this exception will be immediately passed through.
The default options are: { waitMs: 5000, intervalMs: 500 }
function condFn () { return Math.random()*1000 > 995; }
await waitForCondition(condFn);
await waitForCondition(condFn, {
waitMs: 300000,
intervalMs: 10000
});
await waitForCondition(condFn, {
waitMs: 300000,
intervalMs: 10000
logger: myLogger
});
try {
await waitForCondition(condFn, {
error: 'Unable to satisfy condition'
});
} catch (err) {
}
const error = new Error('Unable to satisfy condition');
try {
await waitForCondition(condFn, {
error: error
});
} catch (err) {
}
Run the tests
npm test