Skip to content

Commit

Permalink
feat: timeout function
Browse files Browse the repository at this point in the history
Changelog: feature
  • Loading branch information
mrspartak committed Jun 21, 2024
1 parent 545f06c commit 4d5dc7c
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 1 deletion.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [Installation](#Installation)
- [to](#to---Simplified-Promise-Handling-with-Tuples)
- [delay](#delay---Pause-Execution-for-a-Specified-Time)
- [timeout](#timeout---Timeout-a-Promise)

## Installation
```sh
Expand Down Expand Up @@ -78,4 +79,19 @@ for (let i = 0; i < 10; i++) {

// You can also use alias sleep instead of delay
await sleep(1000)
```

### `timeout` - Timeout a Promise

The timeout function allows you to set a maximum time for a promise to resolve. If the promise does not resolve within the specified time, an error is thrown.

```ts
import { timeout } from "@mrspartak/promises"
import { api } from "./api"

// Simple timeout
const user = await timeout(api.get("/me"), 1000) // Timeout after 1 second

// With custom error message
const user = await timeout(api.get("/me"), 1000, "Request timed out")
```
5 changes: 5 additions & 0 deletions examples/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { timeout } from "@mrspartak/promises"
import { api } from "./api"

// Can be used as a race condition
const user = await timeout(api.getUser(), 1000)
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { to } from "./to";
import { delay, sleep } from "./delay";
import { timeout } from "./timeout";

export { to, delay, sleep };
export { to, delay, sleep, timeout };
45 changes: 45 additions & 0 deletions src/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Type alias for a promise of type T.
*/
type TimeoutIn<T> = Promise<T>;

/**
* Return type for the timeout function.
*/
type TimeoutOut<T> = Promise<T>;

/**
* This function ensures that a provided promise is resolved within a specified time.
* If the promise does not resolve within the specified time, an error is thrown.
*
* @template T The type of the value that the input promise resolves to.
* @param {Promise<T>} promise - The promise to be awaited.
* @param {number} ms - The number of milliseconds to wait.
* @param {string} [message] - The error message to throw.
* @returns {Promise<T>} The resolved value of the promise.
* @throws {Error} Throws an error if the input time is not a positive number or if the promise times out.
*
* @includeExample examples/timeout.ts
*/
export async function timeout<T>(promise: TimeoutIn<T>, ms: number, message?: string): TimeoutOut<T> {
// Validate that the input time is a number
if (typeof ms !== 'number') {
throw new Error('timeout(ms) requires a number as a parameter');
}

// Validate that the number is positive
if (ms < 0) {
throw new Error('timeout(ms) requires a positive number as a parameter');
}

// Create a timeout promise that rejects after the specified delay
const timeoutPromise = new Promise<T>((_, reject) => {
const timeoutId = setTimeout(() => {
clearTimeout(timeoutId)
reject(new Error(message || 'Timeout'));
}, ms);
});

// Return a race between the input promise and the timeout promise
return Promise.race([promise, timeoutPromise]);
}
8 changes: 8 additions & 0 deletions test/build/timeout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, expect, it } from "vitest";
import { timeout } from '../../dist/index.js'

describe("timeout", () => {
it("must be a function", () => {
expect(typeof timeout).toBe("function");
});
});
41 changes: 41 additions & 0 deletions test/src/timeout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { timeout } from '../../src/timeout.js'
import { delay } from '../../src/delay.js'

describe('timeout', () => {
it('should be a function', () => {
expect(typeof timeout).toBe('function')
})

it('should return a promise', () => {
expect(timeout(new Promise((resolve) => resolve('value')), 100) instanceof Promise).toBe(true)
})

it('should return the value', async () => {
const value = 'value'
expect(await timeout(new Promise((resolve) => resolve('value')), 100)).toStrictEqual(value)
})

it('should throw an error if time is not a number', async () => {
const error = 'timeout(ms) requires a number as a parameter'
// @ts-ignore
await expect(timeout(new Promise((resolve) => resolve('value')), '100')).rejects.toThrowError(error)
})

it('should throw an error if time is negative', async () => {
const error = 'timeout(ms) requires a positive number as a parameter'
await expect(timeout(new Promise((resolve) => resolve('value')), -100)).rejects.toThrowError(error)
})

it('should throw an error if promise rejects', async () => {
await expect(timeout(new Promise((_, reject) => reject('error')), 100)).rejects.toThrowError('error')
})

it('should throw an error if promise times out', async () => {
await expect(timeout(delay(105), 100)).rejects.toThrowError('Timeout')
})

it('should throw an error if promise times out with custom message', async () => {
await expect(timeout(delay(105), 100, 'custom error')).rejects.toThrowError('custom error')
})
})

0 comments on commit 4d5dc7c

Please sign in to comment.