UNPKG

@barchart/common-js

Version:
217 lines (179 loc) 5.16 kB
import * as assert from './assert.js'; /** * Utilities for working with promises. * * @public * @module lang/promise */ /** * Creates a composite promise which resolves normally or rejects is a specified * amount of time elapses. * * @public * @static * @async * @param {Promise} promise * @param {number} milliseconds * @param {string=} description * @returns {Promise<*>} */ export async function timeout(promise, milliseconds, description) { assert.argumentIsRequired(promise, 'promise', Promise, 'Promise'); assert.argumentIsRequired(milliseconds, 'milliseconds', Number); assert.argumentIsOptional(description, 'description', String); if (!(milliseconds > 0)) { throw 'Unable to configure promise timeout, the "milliseconds" argument must be positive'; } let timeoutToken = null; const timeoutPromise = build((resolveCallback, rejectCallback) => { timeoutToken = setTimeout(() => { rejectCallback(description || `Promise timed out after ${milliseconds} milliseconds`); }, milliseconds); }); const userPromise = (async () => { try { const result = await promise; if (timeoutToken !== null) { clearTimeout(timeoutToken); } return result; } catch (e) { if (timeoutToken !== null) { clearTimeout(timeoutToken); } throw e; } })(); return Promise.race([ userPromise, timeoutPromise ]); } /** * A mapping function that works asynchronously. Given an array of items, each item through * a mapping function, which can return a promise. Then, this function returns a single promise * which is the result of each mapped promise. * * @public * @static * @async * @param {Array} items - The items to map * @param {Function} mapper - The mapping function (e.g. given an item, return a promise). * @param {number=} concurrency - The maximum number of promises that are allowed to run at once. * @returns {Promise<Array>} */ export async function map(items, mapper, concurrency) { assert.argumentIsArray(items, 'items'); assert.argumentIsRequired(mapper, 'mapper', Function); assert.argumentIsOptional(concurrency, 'concurrency', Number); const c = Math.max(0, concurrency || 0); let mapPromise; if (c === 0 || items.length === 0) { mapPromise = Promise.all(items.map((item) => mapper(item))); } else { const total = items.length; let active = 0; let complete = 0; let failure = false; const results = Array.of(total); const executors = items.map((item, index) => { return async () => { const result = await mapper(item); results[index] = result; }; }); mapPromise = build((resolveCallback, rejectCallback) => { const execute = () => { if (!(executors.length > 0 && c > active && !failure)) { return; } active = active + 1; const executor = executors.shift(); (async () => { try { await executor(); if (failure) { return; } active = active - 1; complete = complete + 1; if (complete < total) { execute(); } else { resolveCallback(results); } } catch (error) { failure = true; rejectCallback(error); } })(); execute(); }; execute(); }); } return mapPromise; } /** * Runs a series of functions sequentially (where each function can be * synchronous or asynchronous). The result of each function is passed * to the successive function and the result of the final function is * returned to the consumer. * * @static * @public * @async * @param {Function[]} functions - An array of functions, each expecting a single argument. * @param {*=} input - The argument to pass the first function. * @returns {Promise<*>} */ export async function pipeline(functions, input) { assert.argumentIsArray(functions, 'functions', Function); let result = input; for (let i = 0; i < functions.length; i++) { result = await functions[i](result); } return result; } /** * Given an array of functions, where each returns a promise, runs * the functions in sequential order, until one of the function * returns a successful promise with a non-null result. Any * rejected promise is ignored. * * @public * @async * @param {Function[]} executors * @returns {Promise} */ export async function first(executors) { assert.argumentIsArray(executors, 'executors', Function); let result = null; for (let i = 0; i < executors.length && result === null; i++) { try { result = await executors[i](); } catch { result = null; } } return result; } /** * Creates a new promise, given an executor. * * This is a wrapper for the {@link Promise} constructor; however, any error * is caught and the resulting promise is rejected (instead of letting the * error bubble up to the top-level handler). * * @public * @static * @async * @param {Function} executor - A function which has two callback parameters. The first is used to resolve the promise, the second rejects it. * @returns {Promise} */ export async function build(executor) { return new Promise((resolve, reject) => { try { executor(resolve, reject); } catch(e) { reject(e); } }); }