Cancelable Promises: Why was this proposal withdrawn?
TC39 member here.<p>cwmma gets a lot right here: Promises have always been contentious in JS (and TC39) and Domenic has indeed had the patience of a saint attempting to wrangle the various points of view into a coherent proposal.<p>TC39 as a group is generally very motivated to find positive-sum outcomes and find solutions that address everyone&#x27;s constraints in a satisfactory way. That doesn&#x27;t usually mean design-by-committee: champions work on a coherent design that they feel hangs together, and the committee provides feedback on constraints, not solutions.<p>As a member of TC39, I&#x27;m usually representing ergonomic concerns and the small-company JavaScript developer perspective. I&#x27;ve had a lot of luck, over the years, in giving champions my perspective and letting them come back with an improved proposal.<p>The staging process (which I started sketching out on my blog[1]) has made the back-and-forth easier, which each stage representing further consensus that the constraints of individual members have been incorporated.<p>Unfortunately, I fear that promise cancellation may be a rare design problem with some zero-sum questions.<p>It&#x27;s worth noting that there has been no objection, on the committee, to adding cancellation to the spec in some form.<p>The key questions have been:<p>First. Is cancellation a normal rejection (a regular exception, like from `throw`) or a new kind of abrupt completion (which `finally` would see but not `catch`). The current status quo on the committee, I believe, is that multiple people would have liked to see &quot;third-state&quot; (as Domenic called it) work, but the compatibility issues with it appear fatal.<p>Second. Should promises themselves be cancelled (`promise.cancel()`) or should there be some kind of capability token threaded through promises.<p>What that would look like:<p><pre><code>    let [sendCancel, recvCancel] = CancelToken.pair();
    fetchPerson(person.id, recvCancel);

    async function fetchPerson(id, token) {
      &#x2F;&#x2F; assume fetch is retrofitted with cancel token support
      let person = await fetch(`&#x2F;users&#x2F;${id}`, { token });
    }

    &#x2F;&#x2F; when the cancel button is clicked, cancel the fetch
    cancelButton.onclick = sendCancel;
</code></pre>
This approach had many supporters in the committee, largely because a number of committee members have rejected the idea of `promise.cancel()` (in part because of ideological reasons about giving promise consumers the power to affect other promise consumers, in part because of a problem[2] Domenic raised early about the timing of cancellation, and in part because C# uses cancel tokens[3]).<p>In practice, this would mean that intermediate async functions would need to thread through cancel tokens, which is something that bothered me a lot.<p>For example, it would have this affect on Ember, if we wanted to adopt cancel tokens:<p><pre><code>    &#x2F;&#x2F; routes&#x2F;person.js

    export default class extends Route {
      async model(id, token) {
        return fetch(`&#x2F;person&#x2F;id`, { token });
      }
    }
</code></pre>
In other words, any async hook (or callback) would need to manually thread tokens through. In Ember, we&#x27;d like to be able to cancel async tasks that were initiated for a previous screen or for a part of the screen that the user has navigated away from.<p>In this case, if the user forgot to take the cancel token (which would likely happen all the time in practice), we would simply have no way to cancel the ongoing async.<p>We noticed this problem when designing ember-concurrency[4] (by the venerable Alex Matchneer), and chose to use generators instead, which are more flexible than async functions, and can be cancelled from the outside.<p>At last week&#x27;s Ember Face to Face, we discussed this problem, and decided that the ergonomic problems with using cancel tokens in hooks were sufficiently bad that we are unlikely to use async functions for Ember hooks if cancellation requires manually propagating cancel tokens. Instead, we&#x27;d do this:<p><pre><code>    &#x2F;&#x2F; routes&#x2F;person.js

    export default class extends Route {
      *model(id) {
        return fetch(`&#x2F;person&#x2F;id`);
      }
    }
</code></pre>
The `*` is a little more cryptic, but it&#x27;s actually shorter than `async`, and doesn&#x27;t require people to thread cancel token through APIs.<p>Also notable: because JavaScript doesn&#x27;t have overloading (unlike C#), it is difficult to establish a convention for where to put the cancel token (&quot;last parameter&quot;, vs. &quot;the name `token` in the last parameter as an options bag&quot; vs. &quot;first parameter&quot;). Because cancellation needs to be retrofitted onto a number of existing promise-producing APIs, no one solution works. This makes creating general purpose libraries that work with &quot;promise-producing functions that can be cancelled&quot; almost impossible.<p>The last bit (since I started talking about Ember) is my personal opinion on cancel tokens. On the flip side, a number of people on the committee have a very strongly held belief that cancel tokens are the only way to avoid leaking powerful capabilities to promise consumers.<p>A third option, making a new Task subclass of Promise that would have added cancellation capabilities, was rejected early on the grounds that it would bifurcate the ecosystem and just mean that everyone had to use Task instead of Promise. I personally think we rejected that option too early. It may be the case that Task is the right general-purpose answer, but people with concerns about leaking capabilities to multiple consumers should cast their Tasks to Promises before passing them around.<p>As I said, I think this may be a (very, very) rare case where a positive-sum outcome is impossible, and where we need, as a committee, to discuss what options are available that would minimize the costs of making a particular decision. Unfortunately, we&#x27;re not there yet.<p>Domenic has done a great job herding the perspective cats here, and I found his presentations on this topic always enlightening. I hope the committee can be honest enough about the competing goals in the problem of cancellation so that Domenic will feel comfortable participating again on this topic.<p>[1]: <a href="https:&#x2F;&#x2F;thefeedbackloop.xyz&#x2F;tc39-a-process-sketch-stages-0-and-1&#x2F;" rel="nofollow">https:&#x2F;&#x2F;thefeedbackloop.xyz&#x2F;tc39-a-process-sketch-stages-0-a...</a><p>[2]: <a href="https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposal-cancelable-promises&#x2F;issues&#x2F;8" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;tc39&#x2F;proposal-cancelable-promises&#x2F;issues&#x2F;...</a><p>[3]: <a href="https:&#x2F;&#x2F;msdn.microsoft.com&#x2F;en-us&#x2F;library&#x2F;dd997289(v=vs.110).aspx" rel="nofollow">https:&#x2F;&#x2F;msdn.microsoft.com&#x2F;en-us&#x2F;library&#x2F;dd997289(v=vs.110)....</a><p>[4]: <a href="http:&#x2F;&#x2F;ember-concurrency.com&#x2F;#&#x2F;docs" rel="nofollow">http:&#x2F;&#x2F;ember-concurrency.com&#x2F;#&#x2F;docs</a>