-
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:
CancellationError
is an error used to reject cancelled promises.onCancelled
is 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
onCancelled
with no arguments - if
onCancelled
throws an error it rejects the promise with that error. - If
onCancelled
does 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
cancel
on thesource
promise. - Call
cancel
on anychild
promises.
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
name
property 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);
}
}