04

Tasks: unicast, and therefore cancelable

Promises can't abort work in progress, because that would let one consumer interfere with another. A task trades broadcast for control: one subscriber at a time, explicit fork() for sharing, and information flowing upstream as a cancellation.
A task has mostly the same form and features as a promise, but is unicast by default and can be cancelled. … If all subscribers have unsubscribed and no further subscribers can be introduced, a task can abort its work.
Start both, then cancel consumers one at a time and watch the work.

Task unicast cancelable

the work (an expensive job upstream)idle
task.fork() × 3

Each consumer holds its own fork. Cancelling one fork only unsubscribes that consumer. When the last fork is cancelled no one can ever observe the result, so the task aborts the work upstream.

Promise broadcast not cancelable

the work (an expensive job upstream)idle
promise.then() × 3

A consumer can stop listening, but it can't stop the producer: other consumers might depend on the same result, and no consumer may interfere with another.

Unicast: a task has exactly one observer

A task refuses a second subscriber: sharing must be explicit, via fork().

The shape of a task

var task = new Task(function (resolver, signal) {
  doExpensiveWork({ signal }).then(resolver.return, resolver.throw);
});

var a = task.fork();
var b = task.fork();
a.done(render);
b.done(render);

a.throw(new Error("Never mind"));  // b still waiting, work continues
b.throw(new Error("Never mind"));  // no subscribers left → work aborts

var promise = Promise.resolve(task); // coerce to a shareable promise
  • why it mattersCancellation needs ownership. Because a task has one observer, that observer can safely say “never mind” — nobody else is relying on the result.
  • why it mattersfork() makes sharing explicit and reference-counts interest. The work stops only when the last fork is cancelled, never sooner.
  • why it mattersCancelling a task is the singular dual of iterator.throw() on a generator, and foreshadows a stream reader telling its writer to stop.