diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..a4cda1e --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,135 @@ +import PCancelable from "p-cancelable"; +export interface ClearablePromise extends Promise { + /** + Clear the timeout. + */ + clear: () => void; +} +export type Options = { + /** + Milliseconds before timing out. + + Passing `Infinity` will cause it to never time out. + */ + milliseconds: number; + /** + Do something other than rejecting with an error on timeout. + + You could for example retry: + + @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(), { + milliseconds: 300 + }); + }, + }); + ``` + */ + fallback?: () => ReturnType | Promise; + /** + Specify a custom error message or error. + + If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`. + */ + message?: string | Error; + /** + Custom implementations for the `setTimeout` and `clearTimeout` functions. + + Useful for testing purposes, in particular to work around [`sinon.useFakeTimers()`](https://sinonjs.org/releases/latest/fake-timers/). + + @example + ``` + import pTimeout from 'p-timeout'; + import sinon from 'sinon'; + + const originalSetTimeout = setTimeout; + const originalClearTimeout = clearTimeout; + + sinon.useFakeTimers(); + + // Use `pTimeout` without being affected by `sinon.useFakeTimers()`: + await pTimeout(doSomething(), { + milliseconds: 2000, + customTimers: { + setTimeout: originalSetTimeout, + clearTimeout: originalClearTimeout + } + }); + ``` + */ + readonly customTimers?: { + setTimeout: typeof globalThis.setTimeout; + clearTimeout: typeof globalThis.clearTimeout; + }; + /** + You can abort the promise using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). + + _Requires Node.js 16 or later._ + + @example + ``` + import pTimeout from 'p-timeout'; + import delay from 'delay'; + + const delayedPromise = delay(3000); + + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, 100); + + await pTimeout(delayedPromise, { + milliseconds: 2000, + signal: abortController.signal + }); + ``` + */ + signal?: globalThis.AbortSignal; +}; +/** +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 declare class TimeoutError extends Error { + readonly name: "TimeoutError"; + constructor(message?: string); +} +/** +An error to be thrown when the request is aborted by AbortController. +DOMException is thrown instead of this Error when DOMException is available. +*/ +export declare class AbortError extends Error { + readonly name: "AbortError"; + constructor(message?: string); +} +export default function pTimeout(promise: PromiseLike | PCancelable, options: Options): ClearablePromise; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map new file mode 100644 index 0000000..05d9ba8 --- /dev/null +++ b/dist/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AAEvC,MAAM,WAAW,gBAAgB,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACtD;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,MAAM,MAAM,OAAO,CAAC,UAAU,IAAI;IACjC;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,EAAE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAElD;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAEzB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE;QACvB,UAAU,EAAE,OAAO,UAAU,CAAC,UAAU,CAAC;QACzC,YAAY,EAAE,OAAO,UAAU,CAAC,YAAY,CAAC;KAC7C,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,qBAAa,YAAa,SAAQ,KAAK;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;gBAClB,OAAO,CAAC,EAAE,MAAM;CAI5B;AAED;;;EAGE;AACF,qBAAa,UAAW,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;gBAChB,OAAO,CAAC,EAAE,MAAM;CAK5B;AAsBD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,SAAS,EAAE,UAAU,GAAG,SAAS,EACjE,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC,EACxD,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,GAC1B,gBAAgB,CAAC,SAAS,GAAG,UAAU,CAAC,CAgF1C"} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..d7dc38f --- /dev/null +++ b/dist/index.js @@ -0,0 +1,115 @@ +/** +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; +}