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));
query.abort();
```
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.then(updateView).then(log);
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.
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss