Skip to content

Draft C #7

@ForbesLindesay

Description

@ForbesLindesay

Based on discussions in #6

Terminology

In addition to the terminology from Promises/A+ we use the following:

  1. CancellationError is an error used to reject cancelled promises.
  2. 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:

  1. calls onCancelled with no arguments
  2. if onCancelled throws an error it rejects the promise with that error.
  3. If onCancelled does not throw an error it rejects the promise with a CancellationError

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:

  1. Call cancel on the source promise.
  2. Call cancel on any child promises.

The CancellationError

When a promise is directly cancelled it is rejected with a CancellationError error. A CancellationError must obey the following criteria:

  1. It must be an instance of Error (cancellationError instanceof Error === true).
  2. 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);
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions