// // Copyright 2020 DXOS.org // import { type Context, ContextDisposedError } from '@dxos/context'; import { TimeoutError } from './errors.ts'; /** * Times out after delay. */ export const sleep = (ms: number) => { return new Promise((resolve) => { const finish = Date.now() + ms; // `setTimeout` does not guarantee execution at >= the scheduled time and may execute slightly early. const sleeper = () => { const delta = finish - Date.now(); if (delta > 0) { setTimeout(sleeper, delta); } else { resolve(); } }; sleeper(); }); }; // TODO(burdon): Reconcile with sleep. export const sleepWithContext = (ctx: Context, ms: number) => { const error = new ContextDisposedError(); return new Promise((resolve, reject) => { if (ctx.disposed) { reject(error); return; } const timeout = setTimeout(() => { clearDispose(); resolve(); }, ms); const clearDispose = ctx.onDispose(() => { clearTimeout(timeout); reject(error); }); }); }; /** * Can be used in long-running tasks to let other callbacks be invoked. */ export const asyncReturn = () => sleep(0); /** * Wait for promise or throw error. */ export const asyncTimeout = async (promise: Promise, timeout: number, err?: Error | string): Promise => { if (typeof promise === 'function') { throw new Error('First argument must be a promise.'); } let timeoutId: NodeJS.Timeout; const throwable = err === undefined || typeof err === 'string' ? new TimeoutError(timeout, err) : err; const timeoutPromise = new Promise((resolve, reject) => { timeoutId = setTimeout(() => { reject(throwable); }, timeout); unrefTimeout(timeoutId); }); return await Promise.race([promise, timeoutPromise]).finally(() => { clearTimeout(timeoutId); }); }; /** * In Node.JS, `unref` prevents the timeout from blocking the process from exiting. Not available in browsers. * https://nodejs.org/api/timers.html#timeoutunref */ export const unrefTimeout = (timeoutId: NodeJS.Timeout) => { if (typeof timeoutId === 'object' && 'unref' in timeoutId) { timeoutId.unref(); } };