Support `AbortController` (#26)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
Gyubong 2022-05-27 01:57:52 +09:00 committed by GitHub
parent 0c28612eae
commit 1bf6679148
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 0 deletions

25
index.d.ts vendored
View File

@ -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;
};
/**

View File

@ -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 {

View File

@ -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
View File

@ -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'
});
});
}