UNPKG

@speckle/shared

Version:

Shared code between various Speckle JS packages

120 lines 3.91 kB
import { isNull, isNumber, isUndefined, noop } from '#lodash'; import { ensureError } from './error.js'; export class TimeoutError extends Error { } export class WaitIntervalUntilCanceledError extends Error { } /** * Build promise that can be resolved/rejected manually outside of the promise's execution scope */ export const buildManualPromise = () => { let resolve; // eslint-disable-next-line @typescript-eslint/no-explicit-any let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); const resolveWrapper = (...args) => resolve(...args); const rejectWrapper = (...args) => reject(...args); return { promise, resolve: resolveWrapper, reject: rejectWrapper }; }; export const isNullOrUndefined = (val) => isNull(val) || isUndefined(val); export const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); export const waitIntervalUntil = (ms, predicate) => { const { promise, resolve, reject } = buildManualPromise(); const interval = setInterval(() => { if (predicate()) { clearInterval(interval); resolve(); } }, ms); const ret = promise; ret.cancel = () => { clearInterval(interval); reject(new WaitIntervalUntilCanceledError()); }; return ret; }; /** * Not nullable type guard, useful in `.filter()` calls for proper TS typed * results */ export const isNonNullable = (v) => !!v; /** * Make the promise throw after enough time has passed. Useful for implementing timeout functionality in various flows. */ export const timeoutAt = (ms, optionalMessage) => { // create error beforehand, so we have a better stack trace const err = new TimeoutError(optionalMessage || 'timeoutAt() timed out'); return new Promise((_resolve, reject) => setTimeout(() => { reject(err); }, ms)); }; /** * Invoke and return fn(), but retry it up to n times if it throws */ export const retry = async (fn, n, delayMs) => { let lastError; for (let i = 0; i < n; i++) { try { const res = await Promise.resolve(fn()); return res; } catch (error) { lastError = ensureError(error); if (delayMs && i + 1 < n) { if (isNumber(delayMs)) { await wait(delayMs); } else { await wait(delayMs(i + 1, lastError)); } } } } throw lastError || new Error('Unexpected retry() failure'); }; /** * For quickly profiling a function */ export const profile = async (fn, label, extra) => { const start = performance.now(); const res = await Promise.resolve(fn()); const end = performance.now(); console.log(`[${label || 'profile'}] took ${end - start}ms`, ...(extra ? [extra] : [])); return res; }; /** * For quickly profiling a sync function */ export const profileSync = (fn, label, extra) => { const start = performance.now(); const res = fn(); const end = performance.now(); console.log(`[${label || 'profile'}] took ${end - start}ms`, ...(extra ? [extra] : [])); return res; }; export const removeNullOrUndefinedKeys = (obj) => { const ret = {}; for (const key in obj) { if (!isNullOrUndefined(obj[key])) { ret[key] = obj[key]; } } return ret; }; export const coerceUndefinedValuesToNull = (obj) => { const ret = {}; for (const [key, value] of Object.entries(obj)) { ret[key] = isUndefined(value) ? null : value; } return ret; }; export const isArrayOf = (arr, guard) => Array.isArray(arr) && arr.every(guard); export const waitForever = () => new Promise(noop); /** * Returns true if only one of the arguments is truthy */ export const xor = (a, b) => !!((a || b) && !(a && b)); //# sourceMappingURL=utility.js.map