@speckle/shared
Version:
Shared code between various Speckle JS packages
120 lines • 3.91 kB
JavaScript
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