UNPKG

@dvcol/common-utils

Version:

Typescript library for common utility functions and constants

176 lines (173 loc) 6.17 kB
import { getUUID } from "./chunk-F2ODECNN.js"; // lib/http/utils/fetch.utils.ts var CancellablePromise = class _CancellablePromise extends Promise { _controller = new AbortController(); _debug = false; id = `cancellable-promise-${getUUID()}`; /** * Gets the AbortSignal associated with the AbortController, allowing external code to listen for cancellation events. * * @type {AbortSignal} * @memberof CancellableFetch */ get signal() { return this._controller.signal; } /** * Sets the AbortSignal associated with the AbortController, allowing external code to listen for cancellation events. * @param signal - The AbortSignal to associate with the AbortController. * * @memberof CancellableFetch */ set signal(signal) { this._controller.signal.onabort = signal.onabort; } /** * Cancels the currently active fetch request by aborting the associated AbortController. * * @param {unknown} [reason] - An optional reason for aborting the fetch request. * @memberof CancellableFetch */ cancel(reason) { if (this.signal.aborted) return; if (this._debug) console.error("Promise was cancelled.", { id: this.id }); this._controller.abort(reason); } then(...params) { const promise = super.then(...params); promise.cancel = this.cancel.bind(this); promise.signal = this.signal; return promise; } catch(onRejected) { const promise = super.catch(onRejected); promise.cancel = this.cancel.bind(this); promise.signal = this.signal; return promise; } finally(onFinally) { const promise = super.finally(onFinally); promise.cancel = this.cancel.bind(this); promise.signal = this.signal; return promise; } static isCancellable(promise) { return promise instanceof _CancellablePromise || "cancel" in promise && "signal" in promise; } // eslint-disable-next-line no-dupe-class-members -- To allow method overloading static resolve(value) { return _CancellablePromise.from(Promise.resolve(value)); } static from(promise) { if (_CancellablePromise.isCancellable(promise)) return promise; let _reject; const cancellablePromise = new _CancellablePromise((resolve, reject) => { _reject = reject; promise.then(resolve).catch(reject); }); const onAbort = () => _reject(new DOMException("The operation was aborted.", "AbortError")); cancellablePromise.signal.addEventListener("abort", onAbort); return cancellablePromise; } }; var CancellableFetch = class _CancellableFetch { _controller = new AbortController(); _debug; id = `cancellable-promise-${getUUID()}`; /** * Gets the AbortSignal associated with the AbortController, allowing external code to listen for cancellation events. * * @readonly * @type {AbortSignal} * @memberof CancellableFetch */ get signal() { return this._controller.signal; } /** * Creates an instance of CancellableFetch. * * @param {boolean} [debug=false] - Indicates whether debug information should be logged when a fetch request is cancelled. * @memberof CancellableFetch */ constructor(debug = false) { this._debug = debug; } /** * Performs a fetch request with optional cancellation support. * * @param {RequestInfo | URL} input - The resource URL or a Request object. * @param {Omit<RequestInit, 'signal'>} [init] - Optional RequestInit options, excluding the 'signal' property. * @returns {Promise<Response>} A Promise that resolves to the Response to that request. * @memberof CancellableFetch * @throws {Error} If an error occurs during the fetch operation, other than an 'AbortError'. */ async fetch(input, init) { try { return await fetch(input, { ...init, signal: this._controller.signal }); } catch (error) { if (error instanceof Error && error?.name === "AbortError") { if (this._debug) console.debug("Fetch request was cancelled.", { input, init, error }); return Promise.reject(error); } throw error; } } /** * Performs a fetch request using a new instance of CancellableFetch. * * @static * @template T - The type of the promise result. * @param {RequestInfo | URL} input - The resource URL or a Request object. * @param {Omit<RequestInit, 'signal'>} [init] - Optional RequestInit options, excluding the 'signal' property. * @returns {CancellablePromise<T>} A cancellable promise that resolves to the Response to the fetch request. * @memberof CancellableFetch */ static fetch(input, init) { const cancellableFetch = new _CancellableFetch(); const promise = cancellableFetch.fetch(input, init); const attributes = ["id", "signal"]; const bindAttributes = (_promise) => { attributes.forEach((prop) => { if (prop in _promise) return; Object.defineProperty(_promise, prop, { get: () => cancellableFetch[prop] }); }); }; const methods = ["then", "catch", "finally"]; const bindMethods = (_promise) => { methods.forEach((method) => { const _method = _promise[method]; _promise[method] = (...params) => { const methodPromise = _method.bind(_promise)(...params); if (CancellablePromise.isCancellable(methodPromise)) return methodPromise; bindAttributes(methodPromise); bindMethods(methodPromise); methodPromise.cancel = (...args) => cancellableFetch.cancel(...args); return methodPromise; }; }); }; bindAttributes(promise); bindMethods(promise); promise.cancel = (...args) => cancellableFetch.cancel(...args); return promise; } /** * Cancels the currently active fetch request by aborting the associated AbortController. * * @param {unknown} [reason] - An optional reason for aborting the fetch request. * * @memberof CancellableFetch */ cancel(reason) { if (this.signal.aborted) return; if (this._debug) console.debug("Fetch request was cancelled.", { id: this.id, reason }); this._controller.abort(reason); } }; export { CancellablePromise, CancellableFetch };