p-cancelable
Advanced tools
Comparing version 3.0.0 to 4.0.0
@@ -19,55 +19,9 @@ /** | ||
export interface OnCancelFunction { | ||
(cancelHandler: () => void): void; | ||
shouldReject: boolean; | ||
(cancelHandler: () => void): void; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
export default class PCancelable<ValueType> extends Promise<ValueType> { | ||
/** | ||
Whether the promise is canceled. | ||
*/ | ||
readonly isCanceled: boolean; | ||
/** | ||
Cancel the promise and optionally provide a reason. | ||
The cancellation is synchronous. Calling it after the promise has settled or multiple times does nothing. | ||
@param reason - The cancellation reason to reject the promise with. | ||
*/ | ||
cancel: (reason?: string) => void; | ||
/** | ||
Create a promise that can be canceled. | ||
Can be constructed in the same was as a [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), but with an appended `onCancel` parameter in `executor`. `PCancelable` is a subclass of `Promise`. | ||
Cancelling will reject the promise with `CancelError`. To avoid that, set `onCancel.shouldReject` to `false`. | ||
@example | ||
``` | ||
import PCancelable from 'p-cancelable'; | ||
const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { | ||
const job = new Job(); | ||
onCancel.shouldReject = false; | ||
onCancel(() => { | ||
job.stop(); | ||
}); | ||
job.on('finish', resolve); | ||
}); | ||
cancelablePromise.cancel(); // Doesn't throw an error | ||
``` | ||
*/ | ||
constructor( | ||
executor: ( | ||
resolve: (value?: ValueType | PromiseLike<ValueType>) => void, | ||
reject: (reason?: unknown) => void, | ||
onCancel: OnCancelFunction | ||
) => void | ||
); | ||
/** | ||
Convenience method to make your promise-returning or async function cancelable. | ||
@@ -149,3 +103,3 @@ | ||
Agument5Type, | ||
ReturnType | ||
ReturnType, | ||
>( | ||
@@ -170,2 +124,49 @@ userFn: ( | ||
): (...arguments: unknown[]) => PCancelable<ReturnType>; | ||
/** | ||
Whether the promise is canceled. | ||
*/ | ||
readonly isCanceled: boolean; | ||
/** | ||
Cancel the promise and optionally provide a reason. | ||
The cancellation is synchronous. Calling it after the promise has settled or multiple times does nothing. | ||
@param reason - The cancellation reason to reject the promise with. | ||
*/ | ||
cancel: (reason?: string) => void; | ||
/** | ||
Create a promise that can be canceled. | ||
Can be constructed in the same was as a [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), but with an appended `onCancel` parameter in `executor`. `PCancelable` is a subclass of `Promise`. | ||
Cancelling will reject the promise with `CancelError`. To avoid that, set `onCancel.shouldReject` to `false`. | ||
@example | ||
``` | ||
import PCancelable from 'p-cancelable'; | ||
const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { | ||
const job = new Job(); | ||
onCancel.shouldReject = false; | ||
onCancel(() => { | ||
job.stop(); | ||
}); | ||
job.on('finish', resolve); | ||
}); | ||
cancelablePromise.cancel(); // Doesn't throw an error | ||
``` | ||
*/ | ||
constructor( | ||
executor: ( | ||
resolve: (value?: ValueType | PromiseLike<ValueType>) => void, | ||
reject: (reason?: unknown) => void, | ||
onCancel: OnCancelFunction | ||
) => void | ||
); | ||
} |
83
index.js
@@ -12,28 +12,31 @@ export class CancelError extends Error { | ||
// TODO: Use private class fields when ESLint 8 is out. | ||
const promiseState = Object.freeze({ | ||
pending: Symbol('pending'), | ||
canceled: Symbol('canceled'), | ||
resolved: Symbol('resolved'), | ||
rejected: Symbol('rejected'), | ||
}); | ||
export default class PCancelable { | ||
static fn(userFunction) { | ||
return (...arguments_) => { | ||
return new PCancelable((resolve, reject, onCancel) => { | ||
arguments_.push(onCancel); | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
userFunction(...arguments_).then(resolve, reject); | ||
}); | ||
}; | ||
return (...arguments_) => new PCancelable((resolve, reject, onCancel) => { | ||
arguments_.push(onCancel); | ||
userFunction(...arguments_).then(resolve, reject); | ||
}); | ||
} | ||
#cancelHandlers = []; | ||
#rejectOnCancel = true; | ||
#state = promiseState.pending; | ||
#promise; | ||
#reject; | ||
constructor(executor) { | ||
this._cancelHandlers = []; | ||
this._isPending = true; | ||
this._isCanceled = false; | ||
this._rejectOnCancel = true; | ||
this.#promise = new Promise((resolve, reject) => { | ||
this.#reject = reject; | ||
this._promise = new Promise((resolve, reject) => { | ||
this._reject = reject; | ||
const onResolve = value => { | ||
if (!this._isCanceled || !onCancel.shouldReject) { | ||
this._isPending = false; | ||
if (this.#state !== promiseState.canceled || !onCancel.shouldReject) { | ||
resolve(value); | ||
this.#state = promiseState.resolved; | ||
} | ||
@@ -43,12 +46,14 @@ }; | ||
const onReject = error => { | ||
this._isPending = false; | ||
reject(error); | ||
if (this.#state !== promiseState.canceled || !onCancel.shouldReject) { | ||
reject(error); | ||
this.#state = promiseState.rejected; | ||
} | ||
}; | ||
const onCancel = handler => { | ||
if (!this._isPending) { | ||
throw new Error('The `onCancel` handler was attached after the promise settled.'); | ||
if (this.#state !== promiseState.pending) { | ||
throw new Error(`The \`onCancel\` handler was attached after the promise ${this.#state.description}.`); | ||
} | ||
this._cancelHandlers.push(handler); | ||
this.#cancelHandlers.push(handler); | ||
}; | ||
@@ -58,7 +63,7 @@ | ||
shouldReject: { | ||
get: () => this._rejectOnCancel, | ||
get: () => this.#rejectOnCancel, | ||
set: boolean => { | ||
this._rejectOnCancel = boolean; | ||
} | ||
} | ||
this.#rejectOnCancel = boolean; | ||
}, | ||
}, | ||
}); | ||
@@ -70,31 +75,29 @@ | ||
// eslint-disable-next-line unicorn/no-thenable | ||
then(onFulfilled, onRejected) { | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
return this._promise.then(onFulfilled, onRejected); | ||
return this.#promise.then(onFulfilled, onRejected); | ||
} | ||
catch(onRejected) { | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
return this._promise.catch(onRejected); | ||
return this.#promise.catch(onRejected); | ||
} | ||
finally(onFinally) { | ||
// eslint-disable-next-line promise/prefer-await-to-then | ||
return this._promise.finally(onFinally); | ||
return this.#promise.finally(onFinally); | ||
} | ||
cancel(reason) { | ||
if (!this._isPending || this._isCanceled) { | ||
if (this.#state !== promiseState.pending) { | ||
return; | ||
} | ||
this._isCanceled = true; | ||
this.#state = promiseState.canceled; | ||
if (this._cancelHandlers.length > 0) { | ||
if (this.#cancelHandlers.length > 0) { | ||
try { | ||
for (const handler of this._cancelHandlers) { | ||
for (const handler of this.#cancelHandlers) { | ||
handler(); | ||
} | ||
} catch (error) { | ||
this._reject(error); | ||
this.#reject(error); | ||
return; | ||
@@ -104,4 +107,4 @@ } | ||
if (this._rejectOnCancel) { | ||
this._reject(new CancelError(reason)); | ||
if (this.#rejectOnCancel) { | ||
this.#reject(new CancelError(reason)); | ||
} | ||
@@ -111,3 +114,3 @@ } | ||
get isCanceled() { | ||
return this._isCanceled; | ||
return this.#state === promiseState.canceled; | ||
} | ||
@@ -114,0 +117,0 @@ } |
{ | ||
"name": "p-cancelable", | ||
"version": "3.0.0", | ||
"version": "4.0.0", | ||
"description": "Create a promise that can be canceled", | ||
@@ -15,3 +15,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">=12.20" | ||
"node": ">=14.16" | ||
}, | ||
@@ -46,7 +46,7 @@ "scripts": { | ||
"devDependencies": { | ||
"ava": "^3.15.0", | ||
"ava": "^4.2.0", | ||
"delay": "^5.0.0", | ||
"tsd": "^0.16.0", | ||
"xo": "^0.40.1" | ||
"tsd": "^0.20.0", | ||
"xo": "^0.48.0" | ||
} | ||
} |
@@ -7,9 +7,9 @@ # p-cancelable | ||
*If you target [Node.js 15](https://medium.com/@nodejs/node-js-v15-0-0-is-here-deb00750f278) or later, this package is [less useful](https://github.com/sindresorhus/p-cancelable/issues/27) and you should probably use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) instead.* | ||
*If you target [Node.js 16](https://medium.com/@nodejs/node-js-v15-0-0-is-here-deb00750f278) or later, this package is [less useful](https://github.com/sindresorhus/p-cancelable/issues/27) and you should probably use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) instead.* | ||
## Install | ||
```sh | ||
npm install p-cancelable | ||
``` | ||
$ npm install p-cancelable | ||
``` | ||
@@ -16,0 +16,0 @@ ## Usage |
13520
237