1 of 15

Cancellation:

Room for improvement

Daniel Ehrenberg, Bloomberg

Ron Buckton, Microsoft

June 2024 TC39 meeting, Helsinki

2 of 15

Goals of a cancellation mechanism for JavaScript

  • Avoid unnecessary work
  • Unlink data structures
  • Nesting structure
  • Separation of concerns
  • Easy to thread through
  • Usable from TC39

3 of 15

4 of 15

5 of 15

6 of 15

7 of 15

8 of 15

Does AbortController/AbortSignal meet these goals?

  • Avoid unnecessary work
  • Unlink data structures
  • Nesting structure
  • Separation of concerns
  • Easy to thread through
  • Usable from TC39

9 of 15

Avoid unnecessary work

  • To express "don't care", .abort() an AbortController
  • To propagate this message, pass the AbortSignal in as a parameter
  • To skip ongoing work, register an event hander on the AbortSignal

  • Room for improvement:
    • Not much of a mechanism for refcounting interest, only something to use
    • Bluebird has cancellation as decrementing a refcount; often useful in practice
    • Probably solved by a mechanism complementary to AbortController/AbortSignal
      • E.g., Signal.subtle.unwatched callback might be used to coordinate this refcounting

10 of 15

Unlink data structures

  • AbortSignal can be used to take certain disposal actions which frees memory
  • E.g., remove an event listener for an element which is removed from DOM

  • Room for improvement:
    • Cancel actions may also hold onto things
    • At some point, "past the point of no return", so memory is wasted
    • Add an API to AbortController to drop all cancel reactions
    • Should be implicit/required to pass {once: true} for reactions
    • Shouldn't be possible

11 of 15

Nesting structure

  • AbortSignal.any(signals)
  • Impossible to build in pure JS for memory management reasons
  • Idiom for a "cancellable subtree":

async function foo({signal: outerSignal}) {� let innerController = new AbortController();� let innerSignal = AbortSignal.any([[� innerController.any, outerSignal]]);� fetch(..., {signal: innerSignal});�}

  • Room for improvement:
    • Most JS developers don't know about how AbortSignal.any is designed for this tree structure
    • Consider a convenience API for making a subtree (or more docs)

12 of 15

Separation of concerns

  • AbortController triggers cancel
  • AbortSignal reacts to cancel
  • AbortController is "stronger"

  • Room for improvement:
    • AbortSignal based on EventTarget permits easy accidental misuse
    • Can call dispatchEvent on AbortSignal, but only AbortController should be able to do that!
    • Can overwrite onabort, which unsubscribes any previous handler!
    • Consider unifying built-in DOM reactions with reactions registered from JS (e.g., ordering)

13 of 15

Easy to thread through

  • Just a parameter, easy! (?)
  • Need to update each function in a call stack to pass it through
  • Convention to use signal in the options bag
  • Current low ecosystem adoption might be related to difficulty

  • Room for improvement
    • AsyncContext variable with the current, ambient AbortSignal
    • Platform APIs accept { signal: "inherit" } to use the ambient AbortSignal
    • Maybe even a "cancellable async function" syntax which inserts throwIfAborted() everywhere

14 of 15

Usable from TC39

  • Current status: APIs with cancellation/unsubscription happen outside TC39
  • Actually Signals need unsubscription (Signal.subtle.Watcher.unwatch)

  • Possible strategies for improvement:
    • Define host hooks, such that we can tie into AbortController/AbortSignal (at least reacting)
      • Would mean interoperability of JS requires implementing a particular host API
    • Move AbortController/AbortSignal definition into TC39
      • Very messy to do with EventTarget integration
    • Define parallel API in TC39 for cancellation (which addresses other issues explained here)
      • If we do this, must interop with WHATWG AbortController/AbortSignal cleanly
      • Very rare/unfortunate step in API design; would be nice to avoid
      • AbortController name is unusually unfortunate

15 of 15

Next steps

  • Explain these problems in various forms (explainer, more presentations)
  • Work with both TC39 and WHATWG
  • Examine ecosystem integration points for cancellation
  • Iterate on JS libraries (like Ron's @esfx/async-canceltoken)

  • Want to get involved? Get in touch with Ron or Dan.