Git Product home page Git Product logo

p-cancelable's Introduction

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 16 or later, this package is less useful and you should probably use AbortController instead.

Install

npm install p-cancelable

Usage

import PCancelable from 'p-cancelable';

const cancelablePromise = new PCancelable((resolve, reject, onCancel) => {
	const worker = new SomeLongRunningOperation();

	onCancel(() => {
		worker.close();
	});

	worker.on('finish', resolve);
	worker.on('error', reject);
});

// Cancel the operation after 10 seconds
setTimeout(() => {
	cancelablePromise.cancel('Unicorn has changed its color');
}, 10000);

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;
}

API

new PCancelable(executor)

Same as the Promise constructor, but with an appended onCancel parameter in executor.

Cancelling will reject the promise with CancelError. To avoid that, set onCancel.shouldReject to false.

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

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.fn(fn)

Convenience method to make your promise-returning or async function cancelable.

The function you specify will have onCancel appended to its parameters.

import PCancelable from '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();

CancelError

Type: Error

Rejection reason when .cancel() is called.

It includes a .isCanceled property for convenience.

FAQ

Cancelable vs. Cancellable

In American English, the verb cancel is usually inflected canceled and canceling—with one l. Both a browser API and the Cancelable Promises proposal use this spelling.

What about the official Cancelable Promises proposal?

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.

Related

  • p-progress - Create a promise that reports progress
  • p-lazy - Create a lazy promise that defers execution until .then() or .catch() is called
  • p-signal - Cancel promises using AbortSignal
  • More…

p-cancelable's People

Contributors

alextes avatar amannn avatar astoilkov avatar bendingbender avatar fregante avatar jonschlinkert avatar jopemachine avatar richienb avatar rkesters avatar sindresorhus avatar szmarczak avatar theqabalist avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

p-cancelable's Issues

Convert abortable Promise-returning functions to cancelable promises

Common Promises don't have a .cancel method, you have to create an AbortSignal, maybe one for each, and keep track of them. Then if you want to abort multiple promises at once you have to keep this variable around somehow.

What if p-cancelable or similar could just handle that?

const cancelable = new PCancelable(signal => fetch('/api', {signal}))

This would be a good companion to sindresorhus/p-race#4

Passing cancellation reason to cancel handler

Does it make sense to pass the cancellation reason to the registered cancel handlers, like this:

const promise = new PCancelable((resolve, reject, onCancel) => {
    onCancel(reason => {
        console.log(reason); // <= should print 'foo'
    });
});

promise.cancel('foo');

If it does, I'll make a PR.

Polyfill ?

Can this become a polyfill extends the current Promise class that I can get a cancelable Promise whenever and whereever in my app?

isCanceled is not set to true when shouldReject is set to false and promise is rejected in onCancel

I'm doing some p-cancelable testing to get familiar with it and I noticed that isCanceled is not set to true on the promise when the promise is canceled with shouldReject set to false.

I briefly took a look at the library code but TBH I don't see any reason for this to happen so reporting this issue.

My code is (I know, ugly but as said it is my test code):

import PCancelable from 'p-cancelable';

const cancelablePromise = doJob();

try {
    await sleep(500);

    cancelablePromise.cancel();
}
catch (error) {
    console.log("First main catch error: " + error);
}

try {
    const result = await cancelablePromise;
    console.log("result", result);
} catch (error) {
    console.log("Second main catch error: " + error);

    if (cancelablePromise.isCanceled)
        console.log("Don't worry it was canceled")
}

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

function doJob() {
    return new PCancelable((resolve, reject, onCancel) => {
        try {
            const timeout = setTimeout(() => {
                const result = job();
                resolve(result);
            }, 1000);

            onCancel.shouldReject = false;

            onCancel(() => {
                console.log('canceled');
                clearTimeout(timeout);
                reject(new Error('canceled'));
            });
        }
        catch (error) {
            console.log('Promise catch error: ' + error);
            reject(error);
        }
    });
}

Current behavior:

> node .\index.js
canceled
Second main catch error: Error: canceled

Expected behavior:

> node .\index.js
canceled
Second main catch error: CancelError: Promise was canceled
Don't worry it was canceled

If I remove onCancel.shouldReject = false; then it is fine.

Decorator

Would be useful to expose a decorator when the Decorator proposal is more stable.

I'm thinking something like:

class Unicorn {
	@cancelable
	async rainbow(onCancel, input) {}
}

Just opening this issue so I won't forget. Not something worth doing yet.

How to use this with p-lazy?

I want a Promise that is cancelable and whose executor is lazily evaluated. Is this possible by composing p-cancelable with p-lazy?

At the moment it seems impossible. If I wrap p-lazy with p-cancelable...

const cancelable = new PCancelable((outerResolve, outerReject, onCancel) => {
  const lazy = new PLazy((innerResolve, innerReject) => {
    // Our executor logic goes here
    // use innerResolve, innerReject, onCancel
  })
  lazy.then(outerResolve, outerReject) // Oops
})

...it would no longer be lazy, because calling lazy.then() triggers the executor. On the other hand, if I wrap p-cancelable with p-lazy...

const lazy = new PLazy((outerResolve, outerReject) => {
  const cancelable = new PCancelable((innerResolve, innerReject, onCancel) => {
    // Our executor logic goes here
    // use innerResolve, innerReject, onCancel
  })
  cancelable.then(outerResolve, outerReject)
})

...then we cannot cancel it because lazy does not have a .cancel() method.

Not compatible with global Bluebird promises

This library, and therefore got, can't be used in a program that uses Bluebird promises globally. This is because PCancelable amends its prototype tree to make it a subclass of Promise but never calls the base Promise constructor, making it a false subclass.

The following code demonstrates this:

import Bluebird from 'bluebird';
global.Promise = Bluebird;

import pcancelable from 'p-cancelable';

async function run() {
  return await new pcancelable((resolve, reject) => {
    resolve(true);
  });
}

run().then(_ => console.log(_));

The code produces no output. It should print true.

Support wrapping an existing promise

Hi,

Using the fn function feels a bit unnatural and it was not obvious at first glance how to do what I wanted. Once I understood it was ok.

Would you be ok to support a new constructor that directly takes a promise? It is a quite common usecase

Before

const validateDateRangePromise = PCancelable.fn(() => {
  return validateDateRange(dateRange);
})();

or

const validateDateRangePromise = PCancelable.fn(validateDateRange)(dateRange);

After

const validateDateRangePromise = PCancelable.fromPromise(validateDateRange(dateRange));

About `AbortController`

The readme says the following:

image

However, the AbortController interface is primarily targeted at web requests, and since p-cancelable is useful for a lot more than web requests, it might be worth clarifying that in the readme.

Utility static function to convert executor into a PCancelable

Could be very helpful to have a static method like PCancelable.makeCancelable to convert a method into a PCancelable.

export function makeCancellable<T>(executor: (onCancel: OnCancelFunction) => T | PromiseLike<T>): PCancelable<T> {
    return new PCancelable<T>(
        async (resolve, reject, onCancel) => {
            try {
                resolve(await executor(onCancel));
            } catch (error) {
                reject(error);
            }
        }
    );
}

export function foo() {
    return makeCancellable(_foo);
}

async function _foo(onCancel: OnCancelFunction) {
    // some async action here ...
}

export function goo() {
    return makeCancellable(
        onCancel => {
            // some action here ...
        }
    )
}

React-Native: Error on module init

In a react-native environment (0.57.5) on module import the following error is thrown and causes the app to crash: TypeError: undefined is not a function (evaluating Object.setPrototypeOf(PCancelable.prototype,Promise.prototype)')

If you change the inheritence to ES6 style (PCancelable extends Promise) the error is gone.

TypeScript types don't work for calling .then on a PCancelable promise

I would expect something like the following to typecheck, but it doesn't:

let promise: PCancelable<void> = (new PCancelable((resolve, reject, onCancel) => {
  setTimeout(() => resolve());
})).then(() => console.log("Then function called"));

When I try to do something like this, I get the type error

Type 'Promise<void>' is not assignable to type 'PCancelable<void>'.
  Property 'isCanceled' is missing in type 'Promise<void>'. [2322]

Calling .then on a PCancelable to get a new PCancelable is something that should work, right? I think the reason it's not working is because the type declaration in index.d.ts doesn't override the next (or catch) methods when extending Promise:

declare class PCancelable<ValueType> extends Promise<ValueType> { ... }

【Discussion】Object.setPrototypeOf(PCancelable.prototype, Promise.prototype)

in devdocs: https://devdocs.io/javascript/global_objects/object/setprototypeof

It warns that

Warning: Changing the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, currently a very slow operation in every browser and JavaScript engine. In addition, the effects of altering inheritance are subtle and far-flung, and are not limited to the time spent in the Object.setPrototypeOf(...) statement, but may extend to any code that has access to any object whose [[Prototype]] has been altered. You can read more in JavaScript engine fundamentals: optimizing prototypes.

I am puzzled about the usage of setPrototypeOf with PCancelable.prototype. whether it is a bad practice as warnned?

I think not because it doesn't change the promise.prototype in runtime

how about the following code? we usually need to proxy the original functions and add some logic ourselves

const originalCatch = Promise.prototype.catch;
Promise.prototype.catch = (reject) => {
	console.log("my catch");
	return originalCatch(reject);
};

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.