Cancelable promises proposal

Glen Huang curvedmark at
Mon Aug 3 00:43:07 UTC 2015

I was discussing with Jake Archibald about cancelable promises the other day. I was trying to convince him promise.cancel() might not be a good idea. But that conversation unfortunately leans more towards on how the abort API should be exposed on fetch. I did provide some thoughts on that topic, and think the abort API represents a strong use case for cancelable promises. But I feel what's more important is the underlying control flow design.

So I would like to offer my proposal of cancelable promises here, and would like to ask if you think it's a good idea, or if promise.cancel() is actually a good idea and my understanding of promises is flawed.

I think that promises should be an observation API. In other words, it should be kept as one-way communication. This keeps promises simple and easy to reason about.

When we talk about cancelable promises, what we really want is the ability to:

1. abort the action that the root promise observes.
2. let child promises show disinterest on the result of that action

With the premise that promises should be one-way communication, it's clear that #1 should be achieved with a separate API. For example:

let query = queryDB(sql);
query.done.then(data => console.log(data));

This means you need to have access to that separate API in order to abort the action, instead of just the promise.

And abort() rejects the root promise with a special error object, if it's pending.

This also means the abort API doesn't have to be universal. Each action initiator can design their own APIs to abort that action.

And correspondingly, to create a "cancelable promise" with the Promise constructor, it can be as simple as:

function doAction() {
	let abort;
	let done = new Promise((res, rej) => {
		asyncAction(res, rej);
		abort = () => rej(new AbortError());
	return {done, abort};

For #2, I propose we add a method to Promise.prototype that undos .then() (probably also a sugar to undo .catch()) like removeEventListener undos addEventListener. For example.

let query = queryDB(sql);
let updateView = data => render(data);
let log = data => console.log(data);
query.done.ignore(updateView); // deregister callback, updateView and log will never be called
setTimeout(() => {
	query.done.then(updateView); // unless callback is registered again
}, timeEnoughForQueryToFinish);

You can think it as that each promise keeps a list of its child promises, when the same callback is passed to .ignore() it sets a flag on the corresponding child promise so that when itself resolves/rejects, it won't pass that state to that child promise, unless that the same callback is later registered again.

What do you think of this design? Do you think it covers all of your use cases for cancelable promises?

I have to give credit for Kyle Simpson and Jake Archibald for this idea. Kyle's opposition of sending signal back to the promise vendor, and Jake's argument that we need a way to let an observer signal disinterest greatly clarifies my understanding of promises.

I guess someone has probably expressed this idea somewhere in some way (or in some libraries). Sorry if I missed that.

More information about the es-discuss mailing list