From 923cd4a4f6eba4278e593ca6ff941311b37e1bff Mon Sep 17 00:00:00 2001 From: TENSIILE Date: Sun, 3 May 2026 02:55:26 +0300 Subject: [PATCH] feat(Aborter): adds a debounce option for the try method (#86) --- CHANGELOG.md | 1 + readme.md | 70 +++++++++++++++++++++------- src/modules/aborter/aborter.ts | 48 +++++++++++++------ src/modules/aborter/aborter.types.ts | 4 ++ 4 files changed, 91 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e3144..e82c641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added a new event `settled` to `EventListener` [#84](https://github.com/TENSIILE/saborter/pull/84/changes/34843174ea21c79f83142c328e326cd3dffff3ee) - Added strict typing for the `isAbortError` function where the typeguard targets `AbortError` [#85](https://github.com/TENSIILE/saborter/pull/85/changes/efb8d5faa5029e580127b447c26ec860284f2fde) - Added `Response` exception to the `catch` block when `response.ok` is `false` when using the short `fetch` format [#85](https://github.com/TENSIILE/saborter/pull/85/changes/d096d569aadd3ad6c7aa9e1b08a679e41fb0fe49) +- Added `debounce` option directly to `Aborter's` `try` method ### Bug Fixes diff --git a/readme.md b/readme.md index 9b062dc..51b077d 100644 --- a/readme.md +++ b/readme.md @@ -168,6 +168,8 @@ const results = await aborter.try( Using the `Provision API`, an `aborter` can pass its data to either the `Fetch API` or the `XMLHttpRequest API` without passing it directly. This allows it to work with libraries such as [`axios`](https://www.npmjs.com/package/axios), [`wretch`](https://www.npmjs.com/package/wretch), and [`ky`](https://www.npmjs.com/package/ky): +[Read more](#interrupting-requests-without-direct-signal-transmission-provision-api) + ```javascript // Create an Aborter instance const aborter = new Aborter(); @@ -187,19 +189,20 @@ const results = await aborter.try(() => axios.get('/api/users').then((res) => re ### 4. Built-in debounce functionality -The `Aborter` class allows integration with the debounce utility: +You can use the `debounce` option directly inside the method: -```javascript -import { debounce } from 'saborter/lib'; +[Read more](#working-with-debounce) +```javascript // Create an Aborter instance const aborter = new Aborter(); // The request will be delayed for 2 seconds and then executed. const results = await aborter.try( - debounce((signal) => { - return fetch('/api/long-task', { signal }); - }, 2000) + (signal) => { + return fetch('/api/data', { signal }); + }, + { debounce: 2000 } ); ``` @@ -218,16 +221,6 @@ const results = await aborter.try((signal, { headers }) => { }); ``` -If you want to use `Provision API`, you don't have to transfer anything: - -```javascript -const aborter = new Aborter({ interruptionOnServer: true }); - -const results = await aborter.try(() => { - return fetch('/api/posts'); -}); -``` - ### 6. Interrupting promises without a signal If you want to cancel a task with a promise: @@ -636,7 +629,7 @@ requestPromise.catch((error) => { } }); -// Cancel +// Abort aborter.abort(); ``` @@ -736,7 +729,6 @@ const aborter = new Aborter({ onAbort: (error) => { if (error.type === 'cancelled') { // handling request cancellation via a callback - } } }); @@ -751,6 +743,48 @@ const result = await aborter }); ``` +### Working with debounce + +The first way is the built-in `debounce` option in the `try` method: + +```javascript +// Create an Aborter instance +const aborter = new Aborter(); + +// The request will be delayed for 2 seconds and then executed. +const results = await aborter.try( + (signal) => { + return fetch('/api/data', { signal }); + }, + { debounce: 2000 } +); +``` + +> [!WARNING] +> The difference! This option defers the entire `try` method call, without running anything in the background. + +> [!NOTE] +> We recommend using the option with `debounce` setting via the option. + +You can use debounce in two ways, the second is through the `debounce` lib function: + +```javascript +import { debounce } from 'saborter/lib'; + +// Create an Aborter instance +const aborter = new Aborter(); + +// The request will be delayed for 2 seconds and then executed. +const results = await aborter.try( + debounce((signal) => { + return fetch('/api/data', { signal }); + }, 2000) +); +``` + +> [!WARNING] +> The difference! The function defers the callback itself, while the `try` method executes and logs all the processes that exist under the hood, such as recording an interrupt when the `timeout` expires and changing the request state to `"pending"` + ### Interrupting requests without direct signal transmission (Provision API) If you don't want to pass `Aborter` arguments to the `Fetch API` or `XMLHttpRequest API`, you don't have to. `Aborter` will automatically pass data from its context. diff --git a/src/modules/aborter/aborter.ts b/src/modules/aborter/aborter.ts index 9661a6f..fdc00c6 100644 --- a/src/modules/aborter/aborter.ts +++ b/src/modules/aborter/aborter.ts @@ -3,6 +3,7 @@ import { RequestState, emitRequestState } from '../../features/state-observer'; import { AbortError, isAbortError } from '../../features/abort-error'; import { EventListener, clearEventListeners } from '../../features/event-listener'; import { injectAborterContextIntoHttpRequest, setAborterContextProvisionMode } from '../../features/lib/fetch'; +import { debounce } from '../../features/lib/debounce'; import { ServerBreaker } from '../../features/server-breaker'; import { Timeout, TimeoutError } from '../../features/timeout'; import { ErrorMessage, disposeSymbol } from './aborter.constants'; @@ -113,21 +114,11 @@ export class Aborter implements Types.AborterType { } }; - /** - * Performs an asynchronous request with cancellation of the previous request, preventing the call of the catch block when the request is canceled and the subsequent finally block. - * @param request callback function - * @param options an object that receives a set of settings for performing a request attempt - * @returns Promise - */ - public try(request: Types.AbortableRequest, options?: Types.FnTryOptions): Promise; - - public try(request: Types.AbortableRequest, options?: Types.FnTryOptions): Promise; - - public try( + private tryImpl( request: Types.AbortableRequest, - { isErrorNativeBehavior = false, timeout, unpackData = true, provision = true }: Types.FnTryOptions = {} + { isErrorNativeBehavior, timeout, unpackData, provision }: Types.FnTryOptions = {} ): Promise { - setAborterContextProvisionMode(provision); + setAborterContextProvisionMode(!!provision); if (this.isRequestInProgress) { const cancelledAbortError = new AbortError(ErrorMessage.CancelRequest, { @@ -162,7 +153,7 @@ export class Aborter implements Types.AborterType { Promise.race([ request(this.abortController.signal, this.requestOptions), - Utils.createAbortablePromise(this.abortController.signal, { isErrorNativeBehavior }) + Utils.createAbortablePromise(this.abortController.signal, { isErrorNativeBehavior: !!isErrorNativeBehavior }) ]) .then((response) => { if (!this.isRequestInProgress) @@ -211,6 +202,35 @@ export class Aborter implements Types.AborterType { return promise; } + /** + * Performs an asynchronous request with cancellation of the previous request, preventing the call of the catch block when the request is canceled and the subsequent finally block. + * @param request callback function + * @param options an object that receives a set of settings for performing a request attempt + * @returns Promise + */ + public try(request: Types.AbortableRequest, options?: Types.FnTryOptions): Promise; + + public try(request: Types.AbortableRequest, options?: Types.FnTryOptions): Promise; + + public try( + request: Types.AbortableRequest, + { + isErrorNativeBehavior = false, + timeout, + unpackData = true, + provision = true, + debounce: debounceMs + }: Types.FnTryOptions = {} + ): Promise { + const certainOptions = { isErrorNativeBehavior, timeout, unpackData, provision }; + + if (debounceMs) { + return debounce(() => this.tryImpl(request, certainOptions), debounceMs)(this.signal); + } + + return this.tryImpl(request, certainOptions); + } + /** * Calling this method sets the AbortSignal flag of this object and signals all observers that the associated action should be aborted. */ diff --git a/src/modules/aborter/aborter.types.ts b/src/modules/aborter/aborter.types.ts index 31f83e0..56bed24 100644 --- a/src/modules/aborter/aborter.types.ts +++ b/src/modules/aborter/aborter.types.ts @@ -67,6 +67,10 @@ export interface FnTryOptions { * @default true */ provision?: boolean; + /** + * Delays the call to the `try` method in milliseconds. + */ + debounce?: number; } /**