Abort during delay

This commit is contained in:
2026-08-10 03:52:49 +00:00
parent f07b65511e
commit 6f6bddf560
2 changed files with 69 additions and 1 deletions
+25 -1
View File
@@ -263,7 +263,31 @@ export class ExponentialBackoff {
// Wait before going to the next attempt
const delay = this.#calculateDelay(this.#options, attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
// Wait for the delay or the abort signal
await new Promise((resolve, reject) => {
// Set a timeout to resolve the promise after the delay
let timeout: ReturnType<typeof setTimeout>;
// Handle the abort signal
const abortHandler = () => {
clearTimeout(timeout);
abortController.signal.removeEventListener('abort', abortHandler);
reject(new ExponentialBackoffStoppedRetriesError(abortController.signal.reason));
};
// Handle the timeout
const timeoutHandler = () => {
abortController.signal.removeEventListener('abort', abortHandler);
resolve(undefined);
};
// Set the timeout
timeout = setTimeout(timeoutHandler, delay);
// Add the abort handler to the abort signal
abortController.signal.addEventListener('abort', abortHandler);
})
attempt++;
}
+44
View File
@@ -299,6 +299,49 @@ const testExponentialBackoffRunAbortedStringCreatesError = async (): Promise<voi
expect(abortAndThrowStringFn).not.toHaveResolved();
};
/**
* Tests that the delay is aborted when the abort signal is activated.
*/
const testExponentialBackoffRunDelayAbortedWhenAbortSignal = async (): Promise<void> => {
vi.useFakeTimers();
try {
let abort: (reason: unknown) => void;
const taskFn = vi.fn(async ({ stopRetries }) => {
abort = stopRetries;
throw new Error('error message');
});
// Start the exponential backoff and wait for it to complete
const result = ExponentialBackoff.run(taskFn, () => {}, {
baseDelay: 1000,
jitter: 0,
maxAttempts: 0,
});
// Advance the timer by 500 (mid delay)
await vi.advanceTimersByTimeAsync(500);
// Make sure the abort function is defined (That the taskFn was called)
if (!abort!) {
throw new Error('abort is not defined');
}
// Check that the rsult is still pending
expect(result).not.resolves;
expect(abort).toBeDefined();
// Abort the exponential backoff
abort?.(new Error('exponential backoff aborted'));
// Expect the result to be rejected with an ExponentialBackoffStoppedRetriesError
await expect(result).rejects.toThrow(ExponentialBackoffStoppedRetriesError);
}
finally {
vi.useRealTimers();
}
};
/**
* Tests the {@link ExponentialBackoff.from} factory and subsequent instance {@link ExponentialBackoff.run}
* as an alternative to the static helper.
@@ -617,6 +660,7 @@ const runTests = async (): Promise<void> => {
test('ExponentialBackoff: succeeds and aborts with abort signal', testExponentialBackoffRunSuccessAndAbortSignal);
test('ExponentialBackoff: aborts with abort signal', testExponentialBackoffRunWithAbortSignal);
test('ExponentialBackoff: aborts with aborted string creates error', testExponentialBackoffRunAbortedStringCreatesError);
test('ExponentialBackoff: aborts with abort signal, skipping delay', testExponentialBackoffRunDelayAbortedWhenAbortSignal);
test('ExponentialBackoff: works via from and instance run', testExponentialBackoffFromAndInstanceRun);
test('ExponentialBackoff: retries indefinitely when maxAttempts is 0', testExponentialBackoffRetriesIndefinitelyWhenMaxAttemptsIsZero);
test('ExponentialBackoff: increases delay exponentially between attempts', testExponentialBackoffIncreasesDelayExponentially);