Support `AbortController` (#26)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
0c28612eae
commit
1bf6679148
|
@ -41,6 +41,31 @@ export type Options = {
|
|||
setTimeout: typeof global.setTimeout;
|
||||
clearTimeout: typeof global.clearTimeout;
|
||||
};
|
||||
|
||||
/**
|
||||
You can abort the promise using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
|
||||
|
||||
_Requires Node.js 16 or later._
|
||||
|
||||
@example
|
||||
```
|
||||
import pTimeout from 'p-timeout';
|
||||
import delay from 'delay';
|
||||
|
||||
const delayedPromise = delay(3000);
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, 100);
|
||||
|
||||
await pTimeout(delayedPromise, 2000, undefined, {
|
||||
signal: abortController.signal
|
||||
});
|
||||
```
|
||||
*/
|
||||
signal?: globalThis.AbortSignal;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
42
index.js
42
index.js
|
@ -5,8 +5,39 @@ export class TimeoutError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
An error to be thrown when the request is aborted by AbortController.
|
||||
DOMException is thrown instead of this Error when DOMException is available.
|
||||
*/
|
||||
export class AbortError extends Error {
|
||||
constructor(message) {
|
||||
super();
|
||||
this.name = 'AbortError';
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
TODO: Remove AbortError and just throw DOMException when targeting Node 18.
|
||||
*/
|
||||
const getDOMException = errorMessage => globalThis.DOMException === undefined ?
|
||||
new AbortError(errorMessage) :
|
||||
new DOMException(errorMessage);
|
||||
|
||||
/**
|
||||
TODO: Remove below function and just 'reject(signal.reason)' when targeting Node 18.
|
||||
*/
|
||||
const getAbortedReason = signal => {
|
||||
const reason = signal.reason === undefined ?
|
||||
getDOMException('This operation was aborted.') :
|
||||
signal.reason;
|
||||
|
||||
return reason instanceof Error ? reason : getDOMException(reason);
|
||||
};
|
||||
|
||||
export default function pTimeout(promise, milliseconds, fallback, options) {
|
||||
let timer;
|
||||
|
||||
const cancelablePromise = new Promise((resolve, reject) => {
|
||||
if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
|
||||
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
||||
|
@ -22,6 +53,17 @@ export default function pTimeout(promise, milliseconds, fallback, options) {
|
|||
...options
|
||||
};
|
||||
|
||||
if (options.signal) {
|
||||
const {signal} = options;
|
||||
if (signal.aborted) {
|
||||
reject(getAbortedReason(signal));
|
||||
}
|
||||
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(getAbortedReason(signal));
|
||||
});
|
||||
}
|
||||
|
||||
timer = options.customTimers.setTimeout.call(undefined, () => {
|
||||
if (typeof fallback === 'function') {
|
||||
try {
|
||||
|
|
25
readme.md
25
readme.md
|
@ -103,6 +103,31 @@ await pTimeout(doSomething(), 2000, undefined, {
|
|||
});
|
||||
```
|
||||
|
||||
#### signal
|
||||
|
||||
Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
|
||||
|
||||
You can abort the promise using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
|
||||
|
||||
*Requires Node.js 16 or later.*
|
||||
|
||||
```js
|
||||
import pTimeout from 'p-timeout';
|
||||
import delay from 'delay';
|
||||
|
||||
const delayedPromise = delay(3000);
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
setTimeout(() => {
|
||||
abortController.abort();
|
||||
}, 100);
|
||||
|
||||
await pTimeout(delayedPromise, 2000, undefined, {
|
||||
signal: abortController.signal
|
||||
});
|
||||
```
|
||||
|
||||
### TimeoutError
|
||||
|
||||
Exposed for instance checking and sub-classing.
|
||||
|
|
31
test.js
31
test.js
|
@ -88,3 +88,34 @@ test('`.clear()` method', async t => {
|
|||
await promise;
|
||||
t.true(inRange(end(), {start: 0, end: 350}));
|
||||
});
|
||||
|
||||
/**
|
||||
TODO: Remove if statement when targeting Node.js 16.
|
||||
*/
|
||||
if (globalThis.AbortController !== undefined) {
|
||||
test('rejects when calling `AbortController#abort()`', async t => {
|
||||
const abortController = new AbortController();
|
||||
|
||||
const promise = pTimeout(delay(3000), 2000, undefined, {
|
||||
signal: abortController.signal
|
||||
});
|
||||
|
||||
abortController.abort();
|
||||
|
||||
await t.throwsAsync(promise, {
|
||||
name: 'AbortError'
|
||||
});
|
||||
});
|
||||
|
||||
test('already aborted signal', async t => {
|
||||
const abortController = new AbortController();
|
||||
|
||||
abortController.abort();
|
||||
|
||||
await t.throwsAsync(pTimeout(delay(3000), 2000, undefined, {
|
||||
signal: abortController.signal
|
||||
}), {
|
||||
name: 'AbortError'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue