Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add timeout function #250

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
21 changes: 21 additions & 0 deletions benchmarks/async/timeout.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as _ from 'radashi'

describe('timeout', () => {
bench('with default error message', async () => {
try {
await _.timeout(0)
} catch (_) {}
})

bench('with custom error message', async () => {
try {
await _.timeout(0, 'Optional message')
} catch (_) {}
})

bench('with custom error function', async () => {
try {
await _.timeout(0, () => new Error('Custom error'))
} catch (_) {}
})
})
37 changes: 37 additions & 0 deletions docs/async/timeout.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: timeout
description: Asynchronously rejects after a specified amount of time.
---

### Usage

The `_.timeout` function creates a promise that will reject after a given number of milliseconds. You can provide a custom error message or a function that returns an error to be thrown upon rejection.

```ts
import * as _ from 'radashi'

// Rejects after 1 second with the default "timeout" error message
await _.timeout(1000)

// Rejects after 1 second with a custom error message
await _.timeout(1000, 'Custom timeout message')

// Rejects after 1 second with a custom error object
await _.timeout(1000, () => new Error('Custom error'))
```

### Example with `Promise.race`

One of the most useful ways to use `_.timeout` with `Promise.race` to set a timeout for an asynchronous operation.

```ts
import * as _ from 'radashi'

const someAsyncTask = async () => {
await _.sleep(10_000)
return 'Task completed'
}

// Race between the async task and a timeout of 1 second
await Promise.race([someAsyncTask(), _.timeout(1000, 'Task took too long')])
```
48 changes: 48 additions & 0 deletions src/async/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { isString } from 'radashi'

declare const setTimeout: (fn: () => void, ms: number) => unknown

/**
* Creates a promise that will reject after a specified amount of time.
* You can provide a custom error message or a function that returns an error.
*
* @see https://radashi.js.org/reference/async/timeout
*
* @example
* ```ts
* // Reject after 1000 milliseconds with default message "timeout"
* await timeout(1000)
*
* // Reject after 1000 milliseconds with a custom message
* await timeout(1000, "Optional message")
*
* // Reject after 1000 milliseconds with a custom error
* await timeout(1000, () => new Error("Custom error"))
*
* // Example usage with Promise.race to set a timeout for an asynchronous task
* await Promise.race([
* someAsyncTask(),
* timeout(1000, "Optional message"),
* ])
* ```
*/

export function timeout<E extends Error>(
milliseconds: number,
/**
* The error message to reject with.
*
* @default "timeout"
*/
error: string | (() => E) = 'timeout',
): Promise<void> {
return new Promise((_, rej) =>
setTimeout(() => {
if (isString(error)) {
rej(new Error(error))
} else {
rej(error())
}
}, milliseconds),
)
}
1 change: 1 addition & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export * from './async/parallel.ts'
export * from './async/reduce.ts'
export * from './async/retry.ts'
export * from './async/sleep.ts'
export * from './async/timeout.ts'
export * from './async/tryit.ts'
export * from './async/withResolvers.ts'

Expand Down
56 changes: 56 additions & 0 deletions tests/async/timout.test.ts

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timeout.test.ts is misspelled as timout.test.ts 🙃

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as _ from 'radashi'

describe('timeout', () => {
beforeEach(() => {
vi.useFakeTimers()
})

test('rejects after a specified number of milliseconds', async () => {
const promise = _.timeout(10)

vi.advanceTimersToNextTimerAsync()

await expect(promise).rejects.toThrow('timeout')
})

test('rejects with a custom error message', async () => {
const promise = _.timeout(10, 'custom error message')

vi.advanceTimersToNextTimerAsync()

await expect(promise).rejects.toThrow('custom error message')
})

test('rejects with a custom error function', async () => {
class CustomError extends Error {
constructor() {
super('custom error function')
}
}

const promise = _.timeout(10, () => new CustomError())

vi.advanceTimersToNextTimerAsync()

await expect(promise).rejects.toThrow(CustomError)
await expect(promise).rejects.toThrow('custom error function')
})

describe('with Promise.race', () => {
test('resolves correctly when sleep finishes before timeout', async () => {
const promise = Promise.race([_.sleep(10), _.timeout(100)])

vi.advanceTimersByTime(100)

await expect(promise).resolves.toBeUndefined()
})

test('rejects with timeout when it finishes before sleep', async () => {
const promise = Promise.race([_.sleep(100), _.timeout(10)])

vi.advanceTimersByTime(100)

await expect(promise).rejects.toThrow()
})
})
})
Loading