Skip to content

Instantly share code, notes, and snippets.

@mgtitimoli
Last active February 2, 2017 03:41
Show Gist options
  • Save mgtitimoli/b954819f8a734f46876f5c59dab972f4 to your computer and use it in GitHub Desktop.
Save mgtitimoli/b954819f8a734f46876f5c59dab972f4 to your computer and use it in GitHub Desktop.
Cancellable Promises (an implementation without cancellation tokens)
// API:
//
// Promise.prototype.cancel(...args):
// | Promise<resolved> // if promise was cancellable and no error happened
// | Promise<rejected> // if promise was not cancellable or some error happened
//
// type CancellationHandler = (...args) =>
// | Promise<rejected> // can be returned manually or automatically generated by throwing an error
// | Promise<resolved> // can be returned manually or returning without throwing an error
//
// type CanCancel = (onCancel: ?CancellationHandler) => Promise;
//
// type Executor = (
// resolve: ?function,
// reject: ?function,
// canCancel: ?CanCancel, // calling this function will enable promise to be cancellable
// ) => any;
//
// [constructor] Promise(executor: Executor): Promise
const produceCancellablePromise = allowedConsumerId =>
new Promise((resolve, reject, canCancel) => {
// calling promise.cancel will return a new
// promise that tracks cancellation termination
// so this handler acts like a promise executor
// 1. if an exception is thrown
// - cancellation is prevented
// - error can be caught attaching a handler to catch method
// of the promise returned by promise.cancel
// 2. if no exception is thrown
// - cancellation is allowed
// - attaching a handler to then method of the promise returned by promise.cancel
// will allow to know when cancellation finished and get the corresponding message (if any)
const onCancel = (
// all the args passed to promise.cancel will be received as params here
consumer
) => {
if (consumer.id !== allowedConsumerId) {
throw new Error('consumer not allowed');
}
return 'consumer allowed';
};
// Calling this function, will allow the execution of a new method:
// promise.cancel
// 1. If canCancel is not executed, then any call to promise.cancel
// will return a rejected promise
// 2. Regarding to the cancellation handler
// 2.1. If it is provided then it will be able to:
// 2.1.1. prevent cancellation -> throw an error or return a reject promise
// 2.1.2. allow cancellation -> run without throwing an error or return a resolved promise
// 2.2 if no cancellation handler is provided, then there will be
// no restriction to cancel, and any consumer will be able to do it
canCancel(/* optional */ onCancel);
setTimeout(resolve, 5000);
});
const consumePromise = (consumer, promise) =>
promise
.cancel(consumer)
.then(console.log);
const consumers = {
{id: Symbol('consumer 1')},
{id: Symbol('consumer 2')},
};
const cancellableByConsumer0 = produceCancellablePromise(consumers[0]);
// consumers match => this consumer is able to cancel (see line #23)
consumePromise(cancellableByConsumer0, consumers[0]);
// consumers do not match => this consumer is not able to cancel (see line #23)
consumePromise(cancellableByConsumer0, consumers[1]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment