UNPKG

asyncbox

Version:

A collection of small async/await utilities

206 lines 6.51 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parallel = void 0; exports.sleep = sleep; exports.longSleep = longSleep; exports.retry = retry; exports.retryInterval = retryInterval; exports.nodeify = nodeify; exports.nodeifyAll = nodeifyAll; exports.asyncify = asyncify; exports.asyncmap = asyncmap; exports.asyncfilter = asyncfilter; exports.waitForCondition = waitForCondition; const bluebird_1 = __importDefault(require("bluebird")); const lodash_1 = __importDefault(require("lodash")); const LONG_SLEEP_THRESHOLD = 5000; // anything over 5000ms will turn into a spin /** * An async/await version of setTimeout */ async function sleep(ms) { return await bluebird_1.default.delay(ms); } /** * 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`. * * 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. */ async function longSleep(ms, { thresholdMs = LONG_SLEEP_THRESHOLD, intervalMs = 1000, progressCb = null } = {}) { if (ms < thresholdMs) { return await sleep(ms); } const endAt = Date.now() + ms; let timeLeft; let elapsedMs = 0; do { const pre = Date.now(); await sleep(intervalMs); const post = Date.now(); timeLeft = endAt - post; elapsedMs = elapsedMs + (post - pre); if (lodash_1.default.isFunction(progressCb)) { progressCb({ elapsedMs, timeLeft, progress: elapsedMs / ms }); } } while (timeLeft > 0); } /** * An async/await way of running a method until it doesn't throw an error */ async function retry(times, fn, ...args) { let tries = 0; let done = false; let res = null; while (!done && tries < times) { tries++; try { res = await fn(...args); done = true; } catch (err) { if (tries >= times) { throw err; } } } return res; } /** * 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. */ async function retryInterval(times, sleepMs, fn, ...args) { let count = 0; const wrapped = async () => { count++; let res; try { res = await fn(...args); } catch (e) { // do not pause when finished the last retry if (count !== times) { await sleep(sleepMs); } throw e; } return res; }; return await retry(times, wrapped); } exports.parallel = bluebird_1.default.all; /** * Export async functions (Promises) and import this with your ES5 code to use * it with Node. */ // eslint-disable-next-line promise/prefer-await-to-callbacks function nodeify(promisey, cb) { return bluebird_1.default.resolve(promisey).nodeify(cb); } /** * Node-ify an entire object of `Promise`-returning functions */ function nodeifyAll(promiseyMap) { const cbMap = {}; for (const [name, fn] of lodash_1.default.toPairs(promiseyMap)) { cbMap[name] = function (...args) { const _cb = args.slice(-1)[0]; const fnArgs = args.slice(0, -1); nodeify(fn(...fnArgs), _cb); }; } return cbMap; } /** * Fire and forget async function execution */ function asyncify(fn, ...args) { bluebird_1.default.resolve(fn(...args)).done(); } /** * Similar to `Array.prototype.map`; runs in serial or parallel */ async function asyncmap(coll, mapper, runInParallel = true) { if (runInParallel) { return (0, exports.parallel)(coll.map(mapper)); } const newColl = []; for (const item of coll) { newColl.push(await mapper(item)); } return newColl; } /** * Similar to `Array.prototype.filter` */ async function asyncfilter(coll, filter, runInParallel = true) { const newColl = []; if (runInParallel) { const bools = await (0, exports.parallel)(coll.map(filter)); for (let i = 0; i < coll.length; i++) { if (bools[i]) { newColl.push(coll[i]); } } } else { for (const item of coll) { if (await filter(item)) { newColl.push(item); } } } return newColl; } /** * 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 }` */ async function waitForCondition(condFn, options = {}) { const opts = lodash_1.default.defaults(options, { waitMs: 5000, intervalMs: 500, }); const debug = opts.logger ? opts.logger.debug.bind(opts.logger) : lodash_1.default.noop; const error = opts.error; const begunAt = Date.now(); const endAt = begunAt + opts.waitMs; const spin = async function spin() { const result = await condFn(); if (result) { return result; } const now = Date.now(); const waited = now - begunAt; const remainingTime = endAt - now; if (now < endAt) { debug(`Waited for ${waited} ms so far`); await bluebird_1.default.delay(Math.min(opts.intervalMs, remainingTime)); return await spin(); } // if there is an error option, it is either a string message or an error itself throw error ? lodash_1.default.isString(error) ? new Error(error) : error : new Error(`Condition unmet after ${waited} ms. Timing out.`); }; return await spin(); } //# sourceMappingURL=asyncbox.js.map