Re: [whatwg/dom] AbortSignal consumers can potentialy emit abort event on it (#784)

> Browsers don't use .then() all the time either,

I think it's problematic with other features from ECMA262 that there's hidden features. I strongly wish both hosts (browsers, +others e.g. Node) would use the same interface to things which they are started too (I'll elaborate on this at the bottom).

> but thus far that hasn't happened for events.

But this presupposes the solution for cancellation needed to be events. The primary reason `AbortController` was introduced was so that there'd be a way to cancel fetches, this doesn't require events.

This is something I've noticed and find unfortunate with web standards generally and something I think the TC39 overwhemingly does well.

I see there to be almost a fork with 2 different "Javascripts", the WHATWG and W3C where everything is exposed via WebIDL with separate specs that depend heavily on each other to function. And the TC39 version where everything is self-contained and offers clear extension points but avoids having lots of cross-dependencies.

There's also a significantly different processes, with the TC39 there's a clear process with stages that are extremely thorough for a feature to reach completion, and the WHATWG and W3C where design is less thorough but gets delivered a lot more quickly.

However I do find the consequence of this kinda annoying, for the most part since the stage-system for ECMA2562 was introduced I think the features produced are highly polished, very future proof and widely applicable to their use cases. *However* results do tend to happen a *lot* slower (at times bordering on a snail's pace) and it can a lot of time for simple changes to come to fruition.

On the flip side I've noticed the WHATWG and W3C move significantly faster but at a noticeable drop in quality of solutions that aren't as widely applicable or contain fewer anti-features and footguns.

Relating this to `AbortController`/`AbortSignal` I am aware one of the reasons it was introduced is that the TC39 was moving too slowly on development of Cancellation and `fetch` needed something sooner.

However because of the fairly fast nature of WHATWG's development existing features were used leading to a situation where we have something that is less widely applicable† and less polished* than what would've been produced if this were considered as a fundamental primitive that could be used widely throughout all Javascript code, not just browsers.

This has knock on effects, because in adopting `AbortController` you yourself would then [oppose](https://github.com/tc39/proposal-cancellation/issues/22#issuecomment-422774530) developing something new that *is* widely applicable.

† Because anyone who wants to use `AbortController` as a cancellation now primitive has to not only implement `AbortController` but also DOM Events and DOMExceptions from WebIDL. For a fundamental primitive of cancellation bringing "DOMException" seems almost silly in environments that don't require a DOM.

* Compared to something like Cancellation in C# which is widely supported and ties into all parts of the system well.

---

To just give you an idea of how ridiculous I find it when saying just use `AbortController` as the primitive for cancellation, this is the IDL required to implement a consistent interface:

<details>
<summary>IDL Interfaces required</summary>
  
```webidl
interface AbortController {
  constructor();

  [SameObject] readonly attribute AbortSignal signal;

  void abort();
};

[Exposed=(Window,Worker)]
interface AbortSignal : EventTarget {
  readonly attribute boolean aborted;

  attribute EventHandler onabort;
};

[Exposed=(Window,Worker,AudioWorklet)]
interface EventTarget {
  constructor();

  void addEventListener(DOMString type
, EventListener? callback
, optional (AddEventListenerOptions or boolean) options
 = {});
  void removeEventListener(DOMString type
, EventListener? callback
, optional (EventListenerOptions or boolean) options
 = {});
  boolean dispatchEvent(Event event
);
};

callback interface EventListener {
  void handleEvent
(Event event
);
};

dictionary EventListenerOptions {
  boolean capture = false;
};

dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive = false;
  boolean once = false;
};

[Exposed=(Window,Worker,AudioWorklet)]
interface Event {
  constructor(DOMString type
, optional EventInit eventInitDict
 = {});

  readonly attribute DOMString type;
  readonly attribute EventTarget? target;
  readonly attribute EventTarget? srcElement; // historical
  readonly attribute EventTarget? currentTarget;
  sequence<EventTarget> composedPath();

  const unsigned short NONE = 0;
  const unsigned short CAPTURING_PHASE = 1;
  const unsigned short AT_TARGET = 2;
  const unsigned short BUBBLING_PHASE = 3;
  readonly attribute unsigned short eventPhase;

  void stopPropagation();
           attribute boolean cancelBubble; // historical alias of .stopPropagation
  void stopImmediatePropagation();

  readonly attribute boolean bubbles;
  readonly attribute boolean cancelable;
           attribute boolean returnValue;  // historical
  void preventDefault();
  readonly attribute boolean defaultPrevented;
  readonly attribute boolean composed;

  [Unforgeable] readonly attribute boolean isTrusted;
  readonly attribute DOMHighResTimeStamp timeStamp;

  void initEvent(DOMString type
, optional boolean bubbles
 = false, optional boolean cancelable
 = false); // historical
};

dictionary EventInit {
  boolean bubbles
 = false;
  boolean cancelable
 = false;
  boolean composed
 = false;
};

[Exposed=(Window,Worker),
 Serializable]
interface DOMException { // but see below note about ECMAScript binding
  constructor(optional DOMString message
 = "", optional DOMString name
 = "Error");
  readonly attribute DOMString name;
  readonly attribute DOMString message;
  readonly attribute unsigned short code;

  const unsigned short INDEX_SIZE_ERR = 1;
  const unsigned short DOMSTRING_SIZE_ERR = 2;
  const unsigned short HIERARCHY_REQUEST_ERR = 3;
  const unsigned short WRONG_DOCUMENT_ERR = 4;
  const unsigned short INVALID_CHARACTER_ERR = 5;
  const unsigned short NO_DATA_ALLOWED_ERR = 6;
  const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
  const unsigned short NOT_FOUND_ERR = 8;
  const unsigned short NOT_SUPPORTED_ERR = 9;
  const unsigned short INUSE_ATTRIBUTE_ERR = 10;
  const unsigned short INVALID_STATE_ERR = 11;
  const unsigned short SYNTAX_ERR = 12;
  const unsigned short INVALID_MODIFICATION_ERR = 13;
  const unsigned short NAMESPACE_ERR = 14;
  const unsigned short INVALID_ACCESS_ERR = 15;
  const unsigned short VALIDATION_ERR = 16;
  const unsigned short TYPE_MISMATCH_ERR = 17;
  const unsigned short SECURITY_ERR = 18;
  const unsigned short NETWORK_ERR = 19;
  const unsigned short ABORT_ERR = 20;
  const unsigned short URL_MISMATCH_ERR = 21;
  const unsigned short QUOTA_EXCEEDED_ERR = 22;
  const unsigned short TIMEOUT_ERR = 23;
  const unsigned short INVALID_NODE_TYPE_ERR = 24;
  const unsigned short DATA_CLONE_ERR = 25;
};
```

</details>

This is a crazy amount of complexity an environment needs to add just to add a "primitive" for cancellation.

Especially when all the use cases for cancellation can be done within a fairly tiny interface:

<details>
<summary>Simplied AbortController</summary>

```js
class AbortSignal {
  #getAbortState;
  #addHandler;
  #removeHandler;

  constructor({ getAbortState, addHandler, removeHandler }) {
    this.#addHandler = addHandler;
    this.#removeHandler = removeHandler; 
    this.#getAbortState = getAbortState;
  }

  get aborted() {
    return this.#getAbortState();
  }

  register(abortHandler) {
    this.#addHandler(abortHandler);
  }

  unregister(abortHandler) {
    this.#removeHandler(abortHandler);
  }
}

class AbortController {
  #aborted = false;
  #handlers = new Set();

  constructor() {
    this.signal = new AbortSignal({
      getAbortState: () => this.#aborted,
      addHandler: (abortHandler) => {
        if (!this.#aborted) {
          this.#handlers.add(abortHandler)
        }
      },
      removeHandler: (abortHandler) => this.#handlers.delete(abortHandler),
    });
  }

  abort() {
    if (this.#aborted) return;
    for (const handler of this.#handlers) {
      try {
        handler();
      } catch (err) {
        // Perhaps through an AggregateError?
      }
    }
  }
}
```

</details>

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/784#issuecomment-570147612

Received on Thursday, 2 January 2020 08:52:31 UTC