Skip to content

Commit

Permalink
Move the secondary parameters into the options parameter (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
jopemachine committed Jul 25, 2022
1 parent de29542 commit d7920ca
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 101 deletions.
97 changes: 53 additions & 44 deletions index.d.ts
@@ -1,18 +1,54 @@
/* eslint-disable import/export */

export class TimeoutError extends Error {
readonly name: 'TimeoutError';
constructor(message?: string);
}

export interface ClearablePromise<T> extends Promise<T>{
export interface ClearablePromise<T> extends Promise<T> {
/**
Clear the timeout.
*/
clear: () => void;
}

export type Options = {
export type Options<ReturnType> = {
/**
Milliseconds before timing out.
Passing `Infinity` will cause it to never time out.
*/
milliseconds: number;

/**
Do something other than rejecting with an error on timeout.
You could for example retry:
@example
```
import {setTimeout} from 'timers/promises';
import pTimeout from 'p-timeout';
const delayedPromise = () => setTimeout(200);
await pTimeout(delayedPromise(), {
milliseconds: 50,
fallback: () => {
return pTimeout(delayedPromise(), {
milliseconds: 300
});
},
});
```
*/
fallback?: () => ReturnType | Promise<ReturnType>;

/**
Specify a custom error message or error.
If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`.
*/
message?: string | Error;

/**
Custom implementations for the `setTimeout` and `clearTimeout` functions.
Expand All @@ -29,7 +65,8 @@ export type Options = {
sinon.useFakeTimers();
// Use `pTimeout` without being affected by `sinon.useFakeTimers()`:
await pTimeout(doSomething(), 2000, undefined, {
await pTimeout(doSomething(), {
milliseconds: 2000,
customTimers: {
setTimeout: originalSetTimeout,
clearTimeout: originalClearTimeout
Expand All @@ -38,8 +75,8 @@ export type Options = {
```
*/
readonly customTimers?: {
setTimeout: typeof global.setTimeout;
clearTimeout: typeof global.clearTimeout;
setTimeout: typeof globalThis.setTimeout;
clearTimeout: typeof globalThis.clearTimeout;
};

/**
Expand All @@ -60,7 +97,8 @@ export type Options = {
abortController.abort();
}, 100);
await pTimeout(delayedPromise, 2000, undefined, {
await pTimeout(delayedPromise, {
milliseconds: 2000,
signal: abortController.signal
});
```
Expand All @@ -74,36 +112,6 @@ Timeout a promise after a specified amount of time.
If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out.
@param input - Promise to decorate.
@param milliseconds - Milliseconds before timing out.
@param message - Specify a custom error message or error. If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`. Default: `'Promise timed out after 50 milliseconds'`.
@returns A decorated `input` that times out after `milliseconds` time. It has a `.clear()` method that clears the timeout.
@example
```
import {setTimeout} from 'timers/promises';
import pTimeout from 'p-timeout';
const delayedPromise = setTimeout(200);
await pTimeout(delayedPromise, 50);
//=> [TimeoutError: Promise timed out after 50 milliseconds]
```
*/
export default function pTimeout<ValueType>(
input: PromiseLike<ValueType>,
milliseconds: number,
message?: string | Error,
options?: Options
): ClearablePromise<ValueType>;

/**
Timeout a promise after a specified amount of time.
If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out.
@param input - Promise to decorate.
@param milliseconds - Milliseconds before timing out. Passing `Infinity` will cause it to never time out.
@param fallback - Do something other than rejecting with an error on timeout. You could for example retry.
@returns A decorated `input` that times out after `milliseconds` time. It has a `.clear()` method that clears the timeout.
@example
Expand All @@ -113,14 +121,15 @@ import pTimeout from 'p-timeout';
const delayedPromise = () => setTimeout(200);
await pTimeout(delayedPromise(), 50, () => {
return pTimeout(delayedPromise(), 300);
await pTimeout(delayedPromise(), {
milliseconds: 50,
fallback: () => {
return pTimeout(delayedPromise(), 300);
}
});
```
*/
export default function pTimeout<ValueType, ReturnType>(
export default function pTimeout<ValueType, ReturnType = ValueType>(
input: PromiseLike<ValueType>,
milliseconds: number,
fallback: () => ReturnType | Promise<ReturnType>,
options?: Options
options: Options<ReturnType>
): ClearablePromise<ValueType | ReturnType>;
10 changes: 6 additions & 4 deletions index.js
Expand Up @@ -35,10 +35,12 @@ const getAbortedReason = signal => {
return reason instanceof Error ? reason : getDOMException(reason);
};

export default function pTimeout(promise, milliseconds, fallback, options) {
export default function pTimeout(promise, options) {
let timer;

const cancelablePromise = new Promise((resolve, reject) => {
const {milliseconds, fallback, message} = options;

if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
}
Expand All @@ -65,7 +67,7 @@ export default function pTimeout(promise, milliseconds, fallback, options) {
}

timer = options.customTimers.setTimeout.call(undefined, () => {
if (typeof fallback === 'function') {
if (fallback) {
try {
resolve(fallback());
} catch (error) {
Expand All @@ -75,8 +77,8 @@ export default function pTimeout(promise, milliseconds, fallback, options) {
return;
}

const message = typeof fallback === 'string' ? fallback : `Promise timed out after ${milliseconds} milliseconds`;
const timeoutError = fallback instanceof Error ? fallback : new TimeoutError(message);
const errorMessage = typeof message === 'string' ? message : `Promise timed out after ${milliseconds} milliseconds`;
const timeoutError = message instanceof Error ? message : new TimeoutError(errorMessage);

if (typeof promise.cancel === 'function') {
promise.cancel();
Expand Down
51 changes: 29 additions & 22 deletions index.test-d.ts
Expand Up @@ -2,45 +2,52 @@
import {expectType, expectError} from 'tsd';
import pTimeout, {TimeoutError} from './index.js';

const delayedPromise: () => Promise<string> = async () => {
return new Promise(resolve => {
setTimeout(() => {
resolve('foo');
}, 200);
});
};

pTimeout(delayedPromise(), 50).then(() => 'foo');
pTimeout(delayedPromise(), 50, async () => {
return pTimeout(delayedPromise(), 300);
const delayedPromise: () => Promise<string> = async () => new Promise(resolve => {
setTimeout(() => {
resolve('foo');
}, 200);
});
pTimeout(delayedPromise(), 50).then(value => expectType<string>(value));
pTimeout(delayedPromise(), 50, 'error').then(value =>

pTimeout(delayedPromise(), {milliseconds: 50}).then(() => 'foo');
pTimeout(delayedPromise(), {milliseconds: 50, fallback: async () => pTimeout(delayedPromise(), {milliseconds: 300})});
pTimeout(delayedPromise(), {milliseconds: 50}).then(value => expectType<string>(value));
pTimeout(delayedPromise(), {milliseconds: 50, message: 'error'}).then(value =>
expectType<string>(value)
);
pTimeout(delayedPromise(), 50, new Error('error')).then(value =>
pTimeout(delayedPromise(), {milliseconds: 50, message: new Error('error')}).then(value =>
expectType<string>(value)
);
pTimeout(delayedPromise(), 50, async () => 10).then(value => {
pTimeout(delayedPromise(), {milliseconds: 50, fallback: async () => 10}).then(value => {
expectType<string | number>(value);
});
pTimeout(delayedPromise(), 50, () => 10).then(value => {
pTimeout(delayedPromise(), {milliseconds: 50, fallback: () => 10}).then(value => {
expectType<string | number>(value);
});

const customTimers = {setTimeout, clearTimeout};
pTimeout(delayedPromise(), 50, undefined, {customTimers});
pTimeout(delayedPromise(), 50, 'foo', {customTimers});
pTimeout(delayedPromise(), 50, new Error('error'), {customTimers});
pTimeout(delayedPromise(), 50, () => 10, {});
pTimeout(delayedPromise(), {milliseconds: 50, customTimers});
pTimeout(delayedPromise(), {milliseconds: 50, message: 'foo', customTimers});
pTimeout(delayedPromise(), {milliseconds: 50, message: new Error('error'), customTimers});
pTimeout(delayedPromise(), {milliseconds: 50, fallback: () => 10});

expectError(pTimeout(delayedPromise(), 50, () => 10, {customTimers: {setTimeout}}));
expectError(pTimeout(delayedPromise(), 50, () => 10, {
expectError(pTimeout(delayedPromise(), {
milliseconds: 50,
fallback: () => 10,
customTimers: {
setTimeout
}
}));

expectError(pTimeout(delayedPromise(), {
milliseconds: 50,
fallback: () => 10,
customTimers: {
setTimeout: () => 42, // Invalid `setTimeout` implementation
clearTimeout
}
}));

expectError(pTimeout(delayedPromise(), {})); // `milliseconds` is required

const timeoutError = new TimeoutError();
expectType<TimeoutError>(timeoutError);
34 changes: 20 additions & 14 deletions readme.md
Expand Up @@ -16,14 +16,15 @@ import pTimeout from 'p-timeout';

const delayedPromise = setTimeout(200);

await pTimeout(delayedPromise, 50);
await pTimeout(delayedPromise, {
milliseconds: 50,
});
//=> [TimeoutError: Promise timed out after 50 milliseconds]
```

## API

### pTimeout(input, milliseconds, message?, options?)
### pTimeout(input, milliseconds, fallback?, options?)
### pTimeout(input, options)

Returns a decorated `input` that times out after `milliseconds` time. It has a `.clear()` method that clears the timeout.

Expand All @@ -35,15 +36,19 @@ Type: `Promise`

Promise to decorate.

#### milliseconds
#### options

Type: `object`

##### milliseconds

Type: `number`

Milliseconds before timing out.

Passing `Infinity` will cause it to never time out.

#### message
##### message

Type: `string | Error`\
Default: `'Promise timed out after 50 milliseconds'`
Expand All @@ -52,7 +57,7 @@ Specify a custom error message or error.

If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`.

#### fallback
##### fallback

Type: `Function`

Expand All @@ -66,15 +71,14 @@ import pTimeout from 'p-timeout';

const delayedPromise = () => setTimeout(200);

await pTimeout(delayedPromise(), 50, () => {
return pTimeout(delayedPromise(), 300);
await pTimeout(delayedPromise(), {
milliseconds: 50,
fallback: () => {
return pTimeout(delayedPromise(), 300);
},
});
```

#### options

Type: `object`

##### customTimers

Type: `object` with function properties `setTimeout` and `clearTimeout`
Expand All @@ -95,7 +99,8 @@ const originalClearTimeout = clearTimeout;
sinon.useFakeTimers();

// Use `pTimeout` without being affected by `sinon.useFakeTimers()`:
await pTimeout(doSomething(), 2000, undefined, {
await pTimeout(doSomething(), {
milliseconds: 2000,
customTimers: {
setTimeout: originalSetTimeout,
clearTimeout: originalClearTimeout
Expand Down Expand Up @@ -123,7 +128,8 @@ setTimeout(() => {
abortController.abort();
}, 100);

await pTimeout(delayedPromise, 2000, undefined, {
await pTimeout(delayedPromise, {
milliseconds: 2000,
signal: abortController.signal
});
```
Expand Down

0 comments on commit d7920ca

Please sign in to comment.