-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Based on discussions in #6
Terminology
In addition to the terminology from Promises/A+ we use the following:
CancellationErroris an error used to reject cancelled promises.onCancelledis a function associated with a promise (in an implementation specific manner) that is used to support cancellation propagation as described.
Requirements
Promises can be created in two ways:
Directly created promise
These are promises that are created directly using the API for creating a new promise exposed by the library.
Cancellable promises have an additional onCancelled function provided by the creator of the promise. This can be a no-op, but should typically be used to cancel the underlying operation (e.g. an ajax request).
Directly created promises have a cancel method.
When cancel is called on a pending promise it:
- calls
onCancelledwith no arguments - if
onCancelledthrows an error it rejects the promise with that error. - If
onCancelleddoes not throw an error it rejects the promise with aCancellationError
Promise created by then
When then is called, it creates a new promise. Call this the output promise. Call the promise that then was called on the source promise. Call any promise returned by a callback or errorback a child promise:
var output = source.then(function (res) { return childPromise; },
function (err) { return childPromise; });If source is a cancellable promise then output must be a cancellable promise. The cancel methd on output must have the same behaviour as for a 'Directly created promise'. The onCancelled method associated with the output promise must have the following behaviour:
- Call
cancelon thesourcepromise. - Call
cancelon anychildpromises.
The CancellationError
When a promise is directly cancelled it is rejected with a CancellationError error. A CancellationError must obey the following criteria:
- It must be an instance of Error (
cancellationError instanceof Error === true). - It must have a
nameproperty with value"cancel".
Recommended Extensions
The following two extensions may be provided, and if provided should behave as follows:
Uncancellable
unacnellable should return a new promise which will be fulfilled or rejected in the same way as the current promise but does not have a cancel method or has a no-op in place of cancel.
Fork
fork should return a new cancellable promise (output) which will be fulfilled or rejected in the same way as the current promise (source) but where it won't cancel the source if the output promise is cancelled.
Sample Implementation
An example implementation may make the behavior described above easier to follow.
The following sample extends Promise to produce CancellablePromise and uses ES6 syntax:
class CancellablePromise extends Promise {
constructor(fn, onCancelled) {
super(fn);
this._onCancelled = onCancelled;
}
cancel() {
if (this.isPending() && typeof this._onCancelled === "function") {
try {
this._onCancelled();
} catch (e) {
this._reject(e);
}
}
this._reject(new Cancellation()); // double-rejections are no-ops
}
then(cb, eb, ...args) {
var source = this;
var children = [];
function wrap(fn) {
return function (...args) {
var child = fn(...args);
if (isPromise(child) && typeof child.cancel === 'function') {
children.push(child);
}
return child;
}
}
return new CancellablePromise(
resolve => resolve(super(wrap(cb), wrap(eb), ...args)),
() => {
for (var child of children) child.cancel();
source.cancel();
}
);
}
//optional extension
uncancellable() {
return Promise.prototype.then.call(this);
}
//optional extension
fork() {
return new CancellablePromise(resolve => resolve(this), noop);
}
}