Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/promise/timeout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,20 @@ describe('timeout', () => {
it('returns a reason if a response is received after the specified wait time', async () => {
await expect(timeout(50)).rejects.toThrow('The operation was timed out');
});

it('should reject with AbortError when aborted via AbortSignal', async () => {
const controller = new AbortController();
const { signal } = controller;

setTimeout(() => controller.abort(), 50);

await expect(timeout(1000, { signal })).rejects.toThrow('The operation was aborted');
});

it('should reject immediately if signal is already aborted', async () => {
const controller = new AbortController();
controller.abort();

await expect(timeout(1000, { signal: controller.signal })).rejects.toThrow('The operation was aborted');
});
});
20 changes: 18 additions & 2 deletions src/promise/timeout.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { delay } from './delay.ts';
import { TimeoutError } from '../error/TimeoutError.ts';

interface TimeoutOptions {
signal?: AbortSignal;
}

/**
* Returns a promise that rejects with a `TimeoutError` after a specified delay.
*
* @param {number} ms - The delay duration in milliseconds.
* @param {TimeoutOptions} options - The options object.
* @param {AbortSignal} options.signal - An optional AbortSignal to cancel the timeout.
* @returns {Promise<never>} A promise that rejects with a `TimeoutError` after the specified delay.
* @throws {TimeoutError} Throws a `TimeoutError` after the specified delay.
*
Expand All @@ -14,8 +20,18 @@ import { TimeoutError } from '../error/TimeoutError.ts';
* } catch (error) {
* console.error(error); // Will log 'The operation was timed out'
* }
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
* setTimeout(() => controller.abort(), 50);
* try {
* await timeout(1000, { signal }); // Will be aborted after 50ms
* } catch (error) {
* console.error(error); // Will log 'The operation was aborted'
* }
*/
export async function timeout(ms: number): Promise<never> {
await delay(ms);
export async function timeout(ms: number, { signal }: TimeoutOptions = {}): Promise<never> {
await delay(ms, { signal });
throw new TimeoutError();
}
19 changes: 19 additions & 0 deletions src/promise/withTimeout.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,23 @@ describe('withTimeout', () => {
it('returns a reason if a response is received after the specified wait time', () => {
return expect(withTimeout(() => delay(1000), 50)).rejects.toThrow('The operation was timed out');
});

it('should reject with AbortError when aborted via AbortSignal', async () => {
const controller = new AbortController();

setTimeout(() => controller.abort(), 50);

await expect(
withTimeout(() => delay(1000), 5000, { signal: controller.signal })
).rejects.toThrow('The operation was aborted');
});

it('should reject immediately if signal is already aborted', async () => {
const controller = new AbortController();
controller.abort();

await expect(
withTimeout(() => delay(1000), 5000, { signal: controller.signal })
).rejects.toThrow('The operation was aborted');
});
});
23 changes: 18 additions & 5 deletions src/promise/withTimeout.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { timeout } from './timeout.ts';

interface WithTimeoutOptions {
signal?: AbortSignal;
}

/**
* Executes an async function and enforces a timeout.
*
* If the promise does not resolve within the specified time,
* the timeout will trigger and the returned promise will be rejected.
*
*
* @template T
* @param {() => Promise<T>} run - A function that returns a promise to be executed.
* @param {number} ms - The timeout duration in milliseconds.
* @param {WithTimeoutOptions} options - The options object.
* @param {AbortSignal} options.signal - An optional AbortSignal to cancel the operation.
* @returns {Promise<T>} A promise that resolves with the result of the `run` function or rejects if the timeout is reached.
*
* @example
Expand All @@ -20,11 +25,19 @@ import { timeout } from './timeout.ts';
*
* try {
* const data = await withTimeout(fetchData, 1000);
* console.log(data); // Logs the fetched data if `fetchData` is resolved within 1 second.
* console.log(data); // Logs the fetched data if resolved within 1 second.
* } catch (error) {
* console.error(error); // Will log 'TimeoutError' if `fetchData` is not resolved within 1 second.
* console.error(error); // Will log 'TimeoutError' if not resolved within 1 second.
* }
*
* // With AbortSignal
* const controller = new AbortController();
* const { signal } = controller;
* const data = await withTimeout(async () => {
* const response = await fetch('https://example.com/data', { signal });
* return response.json();
* }, 5000, { signal });
*/
export async function withTimeout<T>(run: () => Promise<T>, ms: number): Promise<T> {
return Promise.race([run(), timeout(ms)]);
export async function withTimeout<T>(run: () => Promise<T>, ms: number, { signal }: WithTimeoutOptions = {}): Promise<T> {
return Promise.race([run(), timeout(ms, { signal })]);
}