@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
191 lines • 7.51 kB
JavaScript
;
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