pax_global_header00006660000000000000000000000064140434760420014516gustar00rootroot0000000000000052 comment=ab7fb34ab7569eb5822548ac6766e2469a48b98d p-cancelable-2.1.1/000077500000000000000000000000001404347604200140255ustar00rootroot00000000000000p-cancelable-2.1.1/.editorconfig000066400000000000000000000002571404347604200165060ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 p-cancelable-2.1.1/.gitattributes000066400000000000000000000000231404347604200167130ustar00rootroot00000000000000* text=auto eol=lf p-cancelable-2.1.1/.github/000077500000000000000000000000001404347604200153655ustar00rootroot00000000000000p-cancelable-2.1.1/.github/funding.yml000066400000000000000000000001661404347604200175450ustar00rootroot00000000000000github: sindresorhus open_collective: sindresorhus custom: https://sindresorhus.com/donate tidelift: npm/p-cancelable p-cancelable-2.1.1/.github/security.md000066400000000000000000000002631404347604200175570ustar00rootroot00000000000000# Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. p-cancelable-2.1.1/.github/workflows/000077500000000000000000000000001404347604200174225ustar00rootroot00000000000000p-cancelable-2.1.1/.github/workflows/main.yml000066400000000000000000000007021404347604200210700ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: - 14 - 12 - 10 - 8 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test p-cancelable-2.1.1/.gitignore000066400000000000000000000000271404347604200160140ustar00rootroot00000000000000node_modules yarn.lock p-cancelable-2.1.1/.npmrc000066400000000000000000000000231404347604200151400ustar00rootroot00000000000000package-lock=false p-cancelable-2.1.1/index.d.ts000066400000000000000000000110301404347604200157210ustar00rootroot00000000000000declare class CancelErrorClass extends Error { readonly name: 'CancelError'; readonly isCanceled: true; constructor(reason?: string); } declare namespace PCancelable { /** Accepts a function that is called when the promise is canceled. You're not required to call this function. You can call this function multiple times to add multiple cancel handlers. */ interface OnCancelFunction { (cancelHandler: () => void): void; shouldReject: boolean; } type CancelError = CancelErrorClass; } declare class PCancelable extends Promise { /** Convenience method to make your promise-returning or async function cancelable. @param fn - A promise-returning function. The function you specify will have `onCancel` appended to its parameters. @example ``` import PCancelable = require('p-cancelable'); const fn = PCancelable.fn((input, onCancel) => { const job = new Job(); onCancel(() => { job.cleanup(); }); return job.start(); //=> Promise }); const cancelablePromise = fn('input'); //=> PCancelable // … cancelablePromise.cancel(); ``` */ static fn( userFn: (onCancel: PCancelable.OnCancelFunction) => PromiseLike ): () => PCancelable; static fn( userFn: ( argument1: Agument1Type, onCancel: PCancelable.OnCancelFunction ) => PromiseLike ): (argument1: Agument1Type) => PCancelable; static fn( userFn: ( argument1: Agument1Type, argument2: Agument2Type, onCancel: PCancelable.OnCancelFunction ) => PromiseLike ): ( argument1: Agument1Type, argument2: Agument2Type ) => PCancelable; static fn( userFn: ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type, onCancel: PCancelable.OnCancelFunction ) => PromiseLike ): ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type ) => PCancelable; static fn( userFn: ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type, argument4: Agument4Type, onCancel: PCancelable.OnCancelFunction ) => PromiseLike ): ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type, argument4: Agument4Type ) => PCancelable; static fn< Agument1Type, Agument2Type, Agument3Type, Agument4Type, Agument5Type, ReturnType >( userFn: ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type, argument4: Agument4Type, argument5: Agument5Type, onCancel: PCancelable.OnCancelFunction ) => PromiseLike ): ( argument1: Agument1Type, argument2: Agument2Type, argument3: Agument3Type, argument4: Agument4Type, argument5: Agument5Type ) => PCancelable; static fn( userFn: (...arguments: unknown[]) => PromiseLike ): (...arguments: unknown[]) => PCancelable; /** 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 = require('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) => void, reject: (reason?: unknown) => void, onCancel: PCancelable.OnCancelFunction ) => void ); /** 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; /** Rejection reason when `.cancel()` is called. It includes a `.isCanceled` property for convenience. */ static CancelError: typeof CancelErrorClass; } export = PCancelable; p-cancelable-2.1.1/index.js000066400000000000000000000042671404347604200155030ustar00rootroot00000000000000'use strict'; class CancelError extends Error { constructor(reason) { super(reason || 'Promise was canceled'); this.name = 'CancelError'; } get isCanceled() { return true; } } class PCancelable { static fn(userFn) { return (...arguments_) => { return new PCancelable((resolve, reject, onCancel) => { arguments_.push(onCancel); // eslint-disable-next-line promise/prefer-await-to-then userFn(...arguments_).then(resolve, reject); }); }; } constructor(executor) { this._cancelHandlers = []; this._isPending = true; this._isCanceled = false; this._rejectOnCancel = true; this._promise = new Promise((resolve, reject) => { this._reject = reject; const onResolve = value => { if (!this._isCanceled || !onCancel.shouldReject) { this._isPending = false; resolve(value); } }; const onReject = error => { this._isPending = false; reject(error); }; const onCancel = handler => { if (!this._isPending) { throw new Error('The `onCancel` handler was attached after the promise settled.'); } this._cancelHandlers.push(handler); }; Object.defineProperties(onCancel, { shouldReject: { get: () => this._rejectOnCancel, set: boolean => { this._rejectOnCancel = boolean; } } }); return executor(onResolve, onReject, onCancel); }); } then(onFulfilled, onRejected) { // eslint-disable-next-line promise/prefer-await-to-then return this._promise.then(onFulfilled, onRejected); } catch(onRejected) { return this._promise.catch(onRejected); } finally(onFinally) { return this._promise.finally(onFinally); } cancel(reason) { if (!this._isPending || this._isCanceled) { return; } this._isCanceled = true; if (this._cancelHandlers.length > 0) { try { for (const handler of this._cancelHandlers) { handler(); } } catch (error) { this._reject(error); return; } } if (this._rejectOnCancel) { this._reject(new CancelError(reason)); } } get isCanceled() { return this._isCanceled; } } Object.setPrototypeOf(PCancelable.prototype, Promise.prototype); module.exports = PCancelable; module.exports.CancelError = CancelError; p-cancelable-2.1.1/index.test-d.ts000066400000000000000000000043771404347604200167160ustar00rootroot00000000000000import {expectType} from 'tsd'; import PCancelable = require('.'); import {OnCancelFunction, CancelError} from '.'; const cancelablePromise: PCancelable = new PCancelable( (resolve, reject, onCancel) => { resolve(1); resolve(Promise.resolve(1)); reject(); reject('foo'); expectType(onCancel); onCancel(() => 'foo'); onCancel.shouldReject = false; } ); cancelablePromise.cancel(); cancelablePromise.cancel('foo'); expectType(cancelablePromise.isCanceled); const function0 = PCancelable.fn(onCancel => { expectType(onCancel); return Promise.resolve(10); }); expectType<() => PCancelable>(function0); const function1 = PCancelable.fn( (parameter1: string, onCancel: OnCancelFunction) => Promise.resolve(10) ); expectType<(parameter1: string) => PCancelable>(function1); const function2 = PCancelable.fn( (parameter1: string, parameter2: boolean, onCancel: OnCancelFunction) => Promise.resolve(10) ); expectType<(parameter1: string, parameter2: boolean) => PCancelable>( function2 ); const function3 = PCancelable.fn( ( parameter1: string, parameter2: boolean, parameter3: number, onCancel: OnCancelFunction ) => { return Promise.resolve(10); } ); expectType< ( parameter1: string, parameter2: boolean, parameter3: number ) => PCancelable >(function3); const function4 = PCancelable.fn( ( parameter1: string, parameter2: boolean, parameter3: number, parameter4: symbol, onCancel: OnCancelFunction ) => { return Promise.resolve(10); } ); expectType< ( parameter1: string, parameter2: boolean, parameter3: number, parameter4: symbol ) => PCancelable >(function4); const function5 = PCancelable.fn( ( parameter1: string, parameter2: boolean, parameter3: number, parameter4: symbol, parameter5: null, onCancel: OnCancelFunction ) => { return Promise.resolve(10); } ); expectType< ( parameter1: string, parameter2: boolean, parameter3: number, parameter4: symbol, parameter5: null ) => PCancelable >(function5); const cancelError = new CancelError(); new CancelError('foo'); expectType(cancelError); expectType<'CancelError'>(cancelError.name); expectType(cancelError.isCanceled); p-cancelable-2.1.1/license000066400000000000000000000021251404347604200153720ustar00rootroot00000000000000MIT License Copyright (c) Sindre Sorhus (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. p-cancelable-2.1.1/package.json000066400000000000000000000014511404347604200163140ustar00rootroot00000000000000{ "name": "p-cancelable", "version": "2.1.1", "description": "Create a promise that can be canceled", "license": "MIT", "repository": "sindresorhus/p-cancelable", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "sindresorhus.com" }, "engines": { "node": ">=8" }, "scripts": { "test": "xo && ava && tsd" }, "files": [ "index.js", "index.d.ts" ], "keywords": [ "promise", "cancelable", "cancel", "canceled", "canceling", "cancellable", "cancellation", "abort", "abortable", "aborting", "cleanup", "task", "token", "async", "function", "await", "promises", "bluebird" ], "devDependencies": { "ava": "^1.4.1", "delay": "^4.1.0", "promise.prototype.finally": "^3.1.0", "tsd": "^0.7.1", "xo": "^0.24.0" } } p-cancelable-2.1.1/readme.md000066400000000000000000000112311404347604200156020ustar00rootroot00000000000000# p-cancelable > Create a promise that can be canceled Useful for animation, loading resources, long-running async computations, async iteration, etc. *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.* ## Install ``` $ npm install p-cancelable ``` ## Usage ```js const PCancelable = require('p-cancelable'); const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { const worker = new SomeLongRunningOperation(); onCancel(() => { worker.close(); }); worker.on('finish', resolve); worker.on('error', reject); }); (async () => { try { console.log('Operation finished successfully:', await cancelablePromise); } catch (error) { if (cancelablePromise.isCanceled) { // Handle the cancelation here console.log('Operation was canceled'); return; } throw error; } })(); // Cancel the operation after 10 seconds setTimeout(() => { cancelablePromise.cancel('Unicorn has changed its color'); }, 10000); ``` ## API ### new PCancelable(executor) Same as the [`Promise` constructor](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise), but with an appended `onCancel` parameter in `executor`.
Cancelling will reject the promise with `PCancelable.CancelError`. To avoid that, set `onCancel.shouldReject` to `false`. ```js const PCancelable = require('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 ``` `PCancelable` is a subclass of `Promise`. #### onCanceled(fn) Type: `Function` Accepts a function that is called when the promise is canceled. You're not required to call this function. You can call this function multiple times to add multiple cancel handlers. ### PCancelable#cancel(reason?) Type: `Function` Cancel the promise and optionally provide a reason. The cancellation is synchronous. Calling it after the promise has settled or multiple times does nothing. ### PCancelable#isCanceled Type: `boolean` Whether the promise is canceled. ### PCancelable.CancelError Type: `Error` Rejection reason when `.cancel()` is called. It includes a `.isCanceled` property for convenience. ### PCancelable.fn(fn) Convenience method to make your promise-returning or async function cancelable. The function you specify will have `onCancel` appended to its parameters. ```js const PCancelable = require('p-cancelable'); const fn = PCancelable.fn((input, onCancel) => { const job = new Job(); onCancel(() => { job.cleanup(); }); return job.start(); //=> Promise }); const cancelablePromise = fn('input'); //=> PCancelable // … cancelablePromise.cancel(); ``` ## FAQ ### Cancelable vs. Cancellable [In American English, the verb cancel is usually inflected canceled and canceling—with one l.](http://grammarist.com/spelling/cancel/)
Both a [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Event/cancelable) and the [Cancelable Promises proposal](https://github.com/tc39/proposal-cancelable-promises) use this spelling. ### What about the official [Cancelable Promises proposal](https://github.com/tc39/proposal-cancelable-promises)? ~~It's still an early draft and I don't really like its current direction. It complicates everything and will require deep changes in the ecosystem to adapt to it. And the way you have to use cancel tokens is verbose and convoluted. I much prefer the more pragmatic and less invasive approach in this module.~~ The proposal was withdrawn. ## p-cancelable for enterprise Available as part of the Tidelift Subscription. The maintainers of p-cancelable and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-p-cancelable?utm_source=npm-p-cancelable&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) ## Related - [p-progress](https://github.com/sindresorhus/p-progress) - Create a promise that reports progress - [p-lazy](https://github.com/sindresorhus/p-lazy) - Create a lazy promise that defers execution until `.then()` or `.catch()` is called - [More…](https://github.com/sindresorhus/promise-fun) p-cancelable-2.1.1/test.js000066400000000000000000000134651404347604200153530ustar00rootroot00000000000000import test from 'ava'; import delay from 'delay'; import promiseFinally from 'promise.prototype.finally'; import PCancelable from '.'; // TODO: Remove this when targeting Node.js 10 promiseFinally.shim(); const fixture = Symbol('fixture'); test('new PCancelable()', async t => { t.plan(5); const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => { t.pass(); }); setTimeout(() => { resolve(fixture); }, 50); }); t.true(cancelablePromise instanceof Promise); t.false(cancelablePromise.isCanceled); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, PCancelable.CancelError); t.true(cancelablePromise.isCanceled); }); test('calling `.cancel()` multiple times', async t => { t.plan(2); const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => { t.pass(); }); setTimeout(() => { resolve(fixture); }, 50); }); cancelablePromise.cancel(); cancelablePromise.cancel(); try { await cancelablePromise; } catch (error) { cancelablePromise.cancel(); t.true(error instanceof PCancelable.CancelError); } }); test('no `.cancel()` call', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => { t.fail(); }); setTimeout(() => { resolve(fixture); }, 50); }); t.is(await cancelablePromise, fixture); }); test('no `onCancel` handler', async t => { t.plan(1); const cancelablePromise = new PCancelable(resolve => { setTimeout(() => { resolve(fixture); }, 50); }); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, PCancelable.CancelError); }); test('does not do anything when the promise is already settled', async t => { t.plan(2); const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => { t.fail(); }); resolve(); }); t.false(cancelablePromise.isCanceled); await cancelablePromise; cancelablePromise.cancel(); t.false(cancelablePromise.isCanceled); }); test('PCancelable.fn()', async t => { t.plan(2); const cancelableFunction = PCancelable.fn(async (input, onCancel) => { onCancel(() => { t.pass(); }); await delay(50); return input; }); const cancelablePromise = cancelableFunction(fixture); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, PCancelable.CancelError); }); test('PCancelable.CancelError', t => { t.true(PCancelable.CancelError.prototype instanceof Error); }); test('rejects when canceled', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => {}); }); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, PCancelable.CancelError); }); test('rejects when canceled after a delay', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => {}); }); setTimeout(() => { cancelablePromise.cancel(); }, 100); await t.throwsAsync(cancelablePromise, PCancelable.CancelError); }); test('supports multiple `onCancel` handlers', async t => { let i = 0; const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel(() => i++); onCancel(() => i++); onCancel(() => i++); }); cancelablePromise.cancel(); try { await cancelablePromise; } catch (_) {} t.is(i, 3); }); test('cancel error includes a `isCanceled` property', async t => { const cancelablePromise = new PCancelable(() => {}); cancelablePromise.cancel(); const err = await t.throwsAsync(cancelablePromise); t.true(err.isCanceled); }); test.cb('supports `finally`', t => { const cancelablePromise = new PCancelable(async resolve => { await delay(1); resolve(); }); cancelablePromise.finally(() => { t.end(); }); }); test('default message with no reason', async t => { const cancelablePromise = new PCancelable(() => {}); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, 'Promise was canceled'); }); test('custom reason', async t => { const cancelablePromise = new PCancelable(() => {}); cancelablePromise.cancel('unicorn'); await t.throwsAsync(cancelablePromise, 'unicorn'); }); test('prevent rejection', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel.shouldReject = false; setTimeout(resolve, 100); }); cancelablePromise.cancel(); await t.notThrowsAsync(cancelablePromise); }); test('prevent rejection and reject later', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel.shouldReject = false; setTimeout(() => reject(new Error('unicorn')), 100); }); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, 'unicorn'); }); test('prevent rejection and resolve later', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel.shouldReject = false; setTimeout(() => resolve('unicorn'), 100); }); t.is(await cancelablePromise, 'unicorn'); }); test('`onCancel.shouldReject` is true by default', async t => { await t.notThrows(() => new PCancelable((resolve, reject, onCancel) => { t.true(onCancel.shouldReject); })); }); test('throws on cancel when `onCancel.shouldReject` is true', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { onCancel.shouldReject = false; onCancel.shouldReject = true; }); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise); }); test('throws immediately as soon as .cancel() is called', async t => { const cancelablePromise = new PCancelable((resolve, reject, onCancel) => { const timeout = setTimeout(() => { resolve(true); }, 10); onCancel.shouldReject = true; onCancel(() => { clearTimeout(timeout); resolve(false); }); }); cancelablePromise.cancel(); await t.throwsAsync(cancelablePromise, { message: 'Promise was canceled' }); });