UNPKG

@etsoo/shared

Version:

TypeScript shared utilities and functions

222 lines (199 loc) 5.76 kB
import { DelayedExecutorType } from "./types/DelayedExecutorType"; const hasRequestAnimationFrame = typeof requestAnimationFrame === "function"; /** * Extend utilities */ export namespace ExtendUtils { /** * Apply mixins, official suggested method * https://www.typescriptlang.org/docs/handbook/mixins.html#understanding-the-sample * @param derivedCtor Mixin target class * @param baseCtors Mixin base classes * @param override Override or not */ export function applyMixins( derivedCtor: any, baseCtors: any[], override = false ) { baseCtors.forEach((baseCtor) => { Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => { if ( name !== "constructor" && (override || !derivedCtor.prototype[name]) ) { Object.defineProperty( derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null) ); } }); }); } /** * Create delayed executor * @param func Function * @param delayMiliseconds Delay miliseconds * @returns Result */ export function delayedExecutor<P extends any[]>( func: (...args: P) => void, delayMiliseconds: number ): DelayedExecutorType<P> { let cancel: (() => void) | undefined; return { /** * Call the function * @param miliseconds Delayed miliseconds for this call * @param args Args */ call(miliseconds?: number, ...args: P) { this.clear(); cancel = waitFor(() => { func(...args); cancel = undefined; }, miliseconds ?? delayMiliseconds); }, /** * Clear */ clear() { if (this.isRunning()) { if (cancel) cancel(); cancel = undefined; } }, /** * Is running or not * @returns Result */ isRunning() { return cancel != null; } }; } /** * Promise handler to catch error * @param promise Promise */ export const promiseHandler = (promise: Promise<unknown>) => promise .then((value) => [value, undefined]) .catch((reason) => Promise.resolve([undefined, reason])); /** * Delay promise * @param delay Delay miniseconds */ export function sleep(delay = 0) { return new Promise<void>((resolve) => { waitFor(resolve, delay); }); } /** * Wait for condition meets and execute callback * requestAnimationFrame to replace setTimeout * @param callback Callback * @param checkReady Check ready, when it's a number as miliseconds, similar to setTimeout * @returns cancel callback */ export function waitFor( callback: () => void, checkReady: ((spanTime: number) => boolean) | number ) { let requestID: number | undefined | NodeJS.Timeout; if (hasRequestAnimationFrame) { let lastTime: number | undefined; function loop(timestamp: number) { // Cancelled if (requestID == null) return; if (lastTime === undefined) { lastTime = timestamp; } const elapsed = timestamp - lastTime; const isReady = typeof checkReady === "number" ? elapsed >= checkReady : checkReady(elapsed); if (isReady) { callback(); } else if (requestID != null) { // May also be cancelled in callback or somewhere requestID = requestAnimationFrame(loop); } } requestID = requestAnimationFrame(loop); } else if (typeof checkReady === "number") { requestID = setTimeout(callback, checkReady); } else { // Bad practice to use setTimeout in this way, only for compatibility const ms = 20; let spanTime = 0; let cr = checkReady; function loop() { // Cancelled if (requestID == null) return; spanTime += ms; if (cr(spanTime)) { callback(); } else if (requestID != null) { // May also be cancelled in callback or somewhere requestID = setTimeout(loop, ms); } } requestID = setTimeout(loop, ms); } return () => { if (requestID) { if (hasRequestAnimationFrame && typeof requestID === "number") { cancelAnimationFrame(requestID); } else { clearTimeout(requestID); } requestID = undefined; } }; } /** * Repeat interval for callback * @param callback Callback * @param miliseconds Miliseconds * @returns cancel callback */ export function intervalFor(callback: () => void, miliseconds: number) { let requestID: number | undefined | NodeJS.Timeout; if (hasRequestAnimationFrame) { let lastTime: number | undefined; function loop(timestamp: number) { // Cancelled if (requestID == null) return; if (lastTime === undefined) { lastTime = timestamp; } const elapsed = timestamp - lastTime; if (elapsed >= miliseconds) { lastTime = timestamp; callback(); } if (requestID != null) { // May also be cancelled in callback or somewhere requestID = requestAnimationFrame(loop); } } requestID = requestAnimationFrame(loop); } else { requestID = setInterval(callback, miliseconds); } return () => { if (requestID) { if (hasRequestAnimationFrame && typeof requestID === "number") { cancelAnimationFrame(requestID); } else { clearInterval(requestID); } requestID = undefined; } }; } }