/** Timeout a promise after a specified amount of time. If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out. @param input - Promise to decorate. @returns A decorated `input` that times out after `milliseconds` time. It has a `.clear()` method that clears the timeout. @example ``` import {setTimeout} from 'node:timers/promises'; import pTimeout from 'p-timeout'; const delayedPromise = () => setTimeout(200); await pTimeout(delayedPromise(), { milliseconds: 50, fallback: () => { return pTimeout(delayedPromise(), 300); } }); ``` */ export class TimeoutError extends Error { name; constructor(message) { super(message); this.name = "TimeoutError"; } } /** An error to be thrown when the request is aborted by AbortController. DOMException is thrown instead of this Error when DOMException is available. */ export class AbortError extends Error { name; constructor(message) { super(); this.name = "AbortError"; this.message = message; } } /** TODO: Remove AbortError and just throw DOMException when targeting Node 18. */ const getDOMException = (errorMessage) => globalThis.DOMException === undefined ? new AbortError(errorMessage) : new DOMException(errorMessage); /** TODO: Remove below function and just 'reject(signal.reason)' when targeting Node 18. */ const getAbortedReason = (signal) => { const reason = signal.reason === undefined ? getDOMException("This operation was aborted.") : signal.reason; return reason instanceof Error ? reason : getDOMException(reason); }; export default function pTimeout(promise, options) { const { milliseconds, fallback, message, customTimers = { setTimeout, clearTimeout }, } = options; let timer; const cancelablePromise = new Promise((resolve, reject) => { if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) { throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``); } if (milliseconds === Number.POSITIVE_INFINITY) { resolve(promise); return; } if (options.signal) { const { signal } = options; if (signal.aborted) { reject(getAbortedReason(signal)); } signal.addEventListener("abort", () => { reject(getAbortedReason(signal)); }); } timer = customTimers.setTimeout.call(undefined, () => { if (fallback) { try { resolve(fallback()); } catch (error) { reject(error); } return; } const errorMessage = typeof message === "string" ? message : `Promise timed out after ${milliseconds} milliseconds`; const timeoutError = message instanceof Error ? message : new TimeoutError(errorMessage); if (typeof promise?.cancel === "function") { promise?.cancel(); } reject(timeoutError); }, milliseconds); (async () => { try { resolve(await promise); } catch (error) { reject(error); } finally { customTimers.clearTimeout.call(undefined, timer); } })(); }); cancelablePromise.clear = () => { customTimers.clearTimeout.call(undefined, timer); timer = undefined; }; return cancelablePromise; }