UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

191 lines 7.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.promiseWithAbort = exports.promiseWithTimeout = exports.retryAsync = exports.sleepAsync = exports.promiseNParallel = exports.promiseAllSequential = exports.promiseOnce = exports.promiseLock = void 0; const objects_1 = require("./objects"); const typecheckers_1 = require("./typecheckers"); function _getGlobal() { let _global = (0, objects_1.getGlobal)("helpers_promises", { promises: {} }); return _global; } /** * Lock all concurrent calls to a resource to one promise for a set duration of time. * @param {string} key - Unique key to identify the promise. * @param {() => Promise<T>} promiseFunc - Function that will return the promise to be run only once. * @param {number} duration - Duration in milliseconds to hold on to the promise result. (default=1000) * @returns {Promise<T>} Returns the single promise that will be fullfilled for all promises with the same key. * @example * // returns Promise<string> * var initTests = await promiseLock<string>("initTests", async () => { ... }, 2); */ async function promiseLock(key, promiseFunc, duration = 1000) { duration = (0, typecheckers_1.isNumber)(duration) && duration >= 1 ? duration : 1000; return promiseOnce(key, promiseFunc).then((result) => { (globalThis || window).setTimeout(() => { _deletePromiseByKey(key); }, duration); return result; }); } exports.promiseLock = promiseLock; /** * Ensures that a promise runs only once * @param {string} key - Unique key to identify the promise. * @param {() => Promise<T>} promiseFunc - Function that will return the promise to be run only once. * @param {(result: T) => Promise<boolean>} isValidResult - Optional function that returns boolean to indicate if the result returned * by the promise is valid and should be kepy in memory. * @returns {Promise<T>} Returns the single promise that will be fullfilled for all promises with the same key. * @example * // returns Promise<string> * var initTests = await promiseOnce<string>("initTests", async () => { ... }); */ async function promiseOnce(key, promiseFunc, isValidResult) { let _global = _getGlobal(); let promises = _global.promises; if ((0, objects_1.hasOwnProperty)(promises, key) && (0, typecheckers_1.isFunction)(isValidResult)) { //we have en existing pending promise... let queuedResult = null; try { queuedResult = await promises[key]; } catch (e) { } if ((await isValidResult(queuedResult)) !== true) { _deletePromiseByKey(key); } } if (!(0, objects_1.hasOwnProperty)(promises, key)) { promises[key] = promiseFunc(); } return promises[key]; } exports.promiseOnce = promiseOnce; /** * Runs all promises in sequential order. * @param {(() => Promise<T>)[]} asyncFuncs - Array of functions that return the promises to fullfill. * @returns {Promise<T[]>} Returns a single promise with a merged array of results that are in the same order as the * provided promise functions */ function promiseAllSequential(asyncFuncs) { if (!Array.isArray(asyncFuncs) || !asyncFuncs.length) { return Promise.resolve([]); } return asyncFuncs.reduce((promiseChain, currentTaskcurrentTask) => (promiseChain.then((result) => { let taskResult = currentTaskcurrentTask(); if ((0, typecheckers_1.isNullOrUndefined)(taskResult) || !(0, typecheckers_1.isFunction)(taskResult.then)) //culprit - found one that did not return a promise?! make one. taskResult = Promise.resolve(); return taskResult.then(Array.prototype.concat.bind(result)); })), Promise.resolve([])); } exports.promiseAllSequential = promiseAllSequential; /** * Runs N promises in parallel. * @param {(() => Promise<T>)[]} asyncFuncs - Array of functions that return the promises to fullfill. * @param {number} [maxParallel] - Max number of promises to run in parallel (default=8). * @returns {Promise<T[]>} Returns a single promise with a merged array of results that are in the same order as the * provided promise functions */ function promiseNParallel(asyncFuncs, maxParallel = 8) { if (!Array.isArray(asyncFuncs) || !asyncFuncs.length) { return Promise.resolve([]); } let startChain = () => { let chainData = []; if (asyncFuncs.length) { let next = (data) => { chainData.push(data); return asyncFuncs.length ? (asyncFuncs.shift())().then(next) : chainData; }; return (asyncFuncs.shift())().then(next); } else { return Promise.resolve(chainData); } }; let chains = []; for (let k = 0; k < maxParallel; k += 1) { chains.push(startChain()); } return Promise.all(chains).then(d => { //flatten results return d.reduce((acc, val) => acc.concat(val), []); }); } exports.promiseNParallel = promiseNParallel; /** * Provides an asnyc sleep function that allows you to delay async/wait calls. * @param {number} [seconds] - Time to sleep in seconds. */ function sleepAsync(seconds) { return new Promise(resolve => { (globalThis || window).setTimeout(() => resolve(), seconds > 0 ? seconds * 1000 : 3000); }); } exports.sleepAsync = sleepAsync; /** * Provides the ability to retry an async function n times with a optional delay between calls * @param {(...args) => Promise<T>} fn - Function to retry, * @param {number} numberOfRetries - Number of times to retry. * @param {number} [seconds] - Delay between retries in seconds (default=1). */ async function retryAsync(fn, numberOfRetries, seconds = 1) { let error = null; for (let i = 0; i < numberOfRetries; i++) { try { error = null; await sleepAsync(i === 0 ? 0 : seconds); return await fn(); } catch (ex) { error = ex; } } if (error) { throw error; } throw new Error(`Failed retrying ${numberOfRetries} times`); } exports.retryAsync = retryAsync; /** * Provides the ability to have a promise/async funciton be exceuted with a timeout * @param {number} fn - Promise to execute. * @param {number} timeout - Timeout length in milliseconds. * @returns {Promise<T>} Returns promise result or the promise is rejected error with a message of 'Timeout'. */ async function promiseWithTimeout(fn, timeout) { return Promise .race([ fn, new Promise((resolve, reject) => setTimeout(() => reject(new Error('Timeout')), timeout)) ]); } exports.promiseWithTimeout = promiseWithTimeout; async function promiseWithAbort(fn, options) { return new Promise((resolve, reject) => { const { signal } = options; if (signal.aborted) { return reject(signal.reason); } signal.addEventListener("abort", () => { reject(signal.reason); }); fn.then((result) => { resolve(result); }); }); } exports.promiseWithAbort = promiseWithAbort; function _deletePromiseByKey(key) { let _global = _getGlobal(); let promises = _global.promises; if ((0, objects_1.hasOwnProperty)(promises, key)) { try { delete promises[key]; return true; } catch { return false; } } } //# sourceMappingURL=promises.js.map