From 0b8cce3fdeeead3631ea5b9f0135831d07909af3 Mon Sep 17 00:00:00 2001 From: Mike West Date: Wed, 5 Apr 2017 13:40:23 +0200 Subject: [PATCH] Define AbortController and AbortSignal classes New APIs for a generic reusable abort mechanism. Both Fetch and Streams will build on this initially. Tests: https://github.com/w3c/web-platform-tests/pull/5960. Fixes part of #438. (This commit is also authored by Jake and Anne.) --- dom.bs | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 241 insertions(+), 19 deletions(-) diff --git a/dom.bs b/dom.bs index e1fa0eaa4..34d023622 100644 --- a/dom.bs +++ b/dom.bs @@ -13,7 +13,7 @@ No Editor: true !Tests: web-platform-tests dom/ (ongoing work) !Translation (non-normative): 日本語 Logo: https://resources.whatwg.org/logo-dom.svg -Abstract: DOM defines a platform-neutral model for events and node trees. +Abstract: DOM defines a platform-neutral model for events, aborting activities, and node trees. Ignored Terms: EmptyString, Array, Document Boilerplate: omit feedback-header, omit conformance @@ -557,7 +557,7 @@ algorithm below. the operation that caused event to be dispatched that it needs to be canceled.
event . {{Event/defaultPrevented}} -
Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancellation, +
Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancelation, and false otherwise.
event . {{Event/composed}} @@ -1413,6 +1413,228 @@ can only be used to influence an ongoing one. +

Aborting ongoing activities

+ +

Though promises do not have a built-in aborting mechanism, many APIs using them require abort +semantics. {{AbortController}} is meant to support these requirements by providing an +{{AbortController/abort()}} method that toggles the state of a corresponding {{AbortSignal}} object. +The API which wishes to support aborting can accept an {{AbortSignal}} object, and use its state to +determine how to proceed. + +

APIs that rely upon {{AbortController}} are encouraged to respond to {{AbortController/abort()}} +by rejecting any unsettled promise with a new {{DOMException}} with [=error name=] "{{AbortError}}". + +

+

A hypothetical doAmazingness({ ... }) method could accept an {{AbortSignal}} object + in order to support aborting as follows: + +


+const controller = new AbortController();
+const signal = controller.signal;
+
+startSpinner();
+
+doAmazingness({ ..., signal })
+  .then(result => ...)
+  .catch(err => {
+    if (err.name == 'AbortError') return;
+    showUserErrorMessage();
+  })
+  .then(() => stopSpinner());
+
+// …
+
+controller.abort();
+ +

doAmazingness could be implemented as follows: + +


+function doAmazingness({signal}) {
+  return new Promise((resolve, reject) => {
+    // Begin doing amazingness, and call resolve(result) when done.
+    // But also, watch for signals:
+    signal.addEventListener('abort', () => {
+      // Stop doing amazingness, and:
+      reject(new DOMException('Aborted', 'AbortError'));
+    });
+  });
+}
+
+ +

APIs that require more granular control could extend both {{AbortController}} and + {{AbortSignal}} objects according to their needs. +

+ + +

Interface {{AbortController}}

+ +
+[Constructor,
+ Exposed=(Window,Worker)]
+interface AbortController {
+  [SameObject] readonly attribute AbortSignal signal;
+
+  void abort();
+};
+ +
+
controller = new AbortController() +
Returns a new controller whose {{AbortController/signal}} is set to a newly + created {{AbortSignal}} object. + +
controller . signal +
Returns the {{AbortSignal}} object associated with this object. + +
controller . abort() +
Invoking this method will set this object's {{AbortSignal}}'s [=AbortSignal/aborted flag=] and + signal to any observers that the associated activity is to be aborted. +
+ +

An {{AbortController}} object has an associated signal (an +{{AbortSignal}} object). + +

The AbortController() constructor, when +invoked, must run these steps: + +

    +
  1. Let signal be a new {{AbortSignal}} object. + +

  2. Let controller be a new {{AbortController}} object whose + signal is signal. + +

  3. Return controller. +

+ +

The signal attribute's getter must return +context object's signal. + +

The abort() method, when invoked, must +signal abort on context object's signal. + + +

Interface {{AbortSignal}}

+ +
+[Exposed=(Window,Worker)]
+interface AbortSignal : EventTarget {
+  readonly attribute boolean aborted;
+
+  attribute EventHandler onabort;
+};
+ +
+
signal . aborted +
Returns true if this {{AbortSignal}}'s {{AbortController}} has signaled to abort, and false + otherwise. +
+ +

An {{AbortSignal}} object has an associated aborted flag. It is unset +unless specified otherwise. + +

An {{AbortSignal}} object has associated abort algorithms, which is a +set of algorithms which are to be executed when its [=AbortSignal/aborted flag=] is +set. Unless specified otherwise, its value is the empty set. + +

To add an algorithm algorithm to an {{AbortSignal}} +object signal, run these steps: + +

    +
  1. If signal's aborted flag is set, then return. + +

  2. Append algorithm to signal's + abort algorithms. +

+ +

To remove an algorithm algorithm from an +{{AbortSignal}} signal, remove algorithm from +signal's abort algorithms. + +

The [=AbortSignal/abort algorithms=] enable APIs with complex +requirements to react in a reasonable way to {{AbortController/abort()}}. For example, a given API's +[=AbortSignal/aborted flag=] might need to be propagated to a cross-thread environment, such as a +service worker. + +

The aborted attribute's getter must return true if +context object's [=AbortSignal/aborted flag=] is set, and false otherwise. + +

Changes to an {{AbortSignal}} object represent the wishes of the corresponding +{{AbortController}} object, but an API observing the {{AbortSignal}} object can chose to ignore +them. For instance, if the operation has already completed. + +

To signal abort, given a {{AbortSignal}} object +signal, run these steps: + +

    +
  1. If signal's [=AbortSignal/aborted flag=] is set, then return. + +

  2. Set signal's [=AbortSignal/aborted flag=]. + +

  3. For each algorithm in signal's + [=AbortSignal/abort algorithms=]: run algorithm. + +

  4. Empty signal's abort algorithms. + +

  5. [=Fire an event=] named abort at signal. +

+ + +

Using {{AbortController}} and {{AbortSignal}} objects in +APIs

+ +

Any web platform API using promises to represent operations that can be aborted must adhere to +the following: + +

+ +
+

The steps for a promise-returning method doAmazingness(options) could be as + follows: + +

    +
  1. Let |p| be [=a new promise=]. + +

  2. +

    If |options|' signal member is present, then: + +

      +
    1. If |options|' signal's [=AbortSignal/aborted flag=] is set, then [=reject=] + |p| with an "{{AbortError}}" {{DOMException}} and return |p|. + +

    2. +

      [=AbortSignal/Add|Add the following abort steps=] to |options|' signal: + +

        +
      1. Stop doing amazing things. + +

      2. [=Reject=] |p| with an "{{AbortError}}" {{DOMException}}. +

      +
    + +
  3. +

    Run these steps [=in parallel=]: + +

      +
    1. Let |amazingResult| be the result of doing some amazing things. + +

    2. [=Resolve=] |p| with |amazingResult|. +

    + +
  4. Return |p|. +

+
+ +

APIs not using promises should still adhere to the above as much as possible. + + +

Nodes

Introduction to "The DOM"

@@ -1931,7 +2153,7 @@ before a child, with an optional suppress observers flag, run
  • If node is a {{DocumentFragment}} node, - remove its + remove its children with the suppress observers flag set. @@ -2103,7 +2325,7 @@ within a parent, run these steps:
    1. Set removedNodes to a list solely containing child. -

    2. Remove child from its parent with the +

    3. Remove child from its parent with the suppress observers flag set.

    @@ -2144,7 +2366,7 @@ To replace all with a node, and a list containing node otherwise. -
  • Remove all +
  • Remove all parent's children, in tree order, with the suppress observers flag set. @@ -2169,8 +2391,7 @@ To pre-remove a child fr
  • If child's parent is not parent, then throw a {{NotFoundError}}. -
  • Remove child - from parent. +
  • Remove child from parent.
  • Return child. @@ -2180,7 +2401,7 @@ To pre-remove a child fr

    Specifications may define removing steps for all or some nodes. The algorithm is passed removedNode, and optionally oldParent, as -indicated in the remove algorithm below. +indicated in the remove algorithm below.

    To remove a node from a parent, with an optional suppress observers flag, run these steps: @@ -2651,7 +2872,7 @@ steps:

    1. If context object's parent is null, then return. -

    2. Remove the context object from context object's +

    3. Remove the context object from context object's parent.

    @@ -3810,8 +4031,8 @@ steps for each descendant exclusive Text node no
    1. Let length be node's length. -
    2. If length is zero, then remove node and continue with the next - exclusive Text node, if any. +
    3. If length is zero, then remove node and continue with the + next exclusive Text node, if any.
    4. Let data be the concatenation of the data of node's contiguous exclusive Text nodes (excluding itself), in @@ -3849,8 +4070,8 @@ steps for each descendant exclusive Text node no
    5. Set currentNode to its next sibling.

    -
  • Remove node's contiguous exclusive Text nodes (excluding - itself), in tree order. +
  • Remove node's contiguous exclusive Text nodes + (excluding itself), in tree order.

    {{Node/normalize()}} does not need to run any @@ -4955,8 +5176,8 @@ these steps:

    1. Let oldDocument be node's node document. -

    2. If node's parent is not null, remove node from its - parent. +

    3. If node's parent is not null, remove node + from its parent.

    4. If document is not oldDocument, then: @@ -7124,7 +7345,7 @@ might itself be modified as part of the mutation to the node tree when e.g. part of the content it represents is mutated. -

      See the insert and remove algorithms, the +

      See the insert and remove algorithms, the {{Node/normalize()}} method, and the replace data and split algorithms for the hairy details. @@ -7193,7 +7414,7 @@ the boundary point's length, inclusive. Algorithms that modify a tree (in particular the insert, -remove, +remove, replace data, and split algorithms) also modify ranges associated with that @@ -7781,7 +8002,7 @@ run these steps:

    5. For each node in nodes to remove, in tree order, - remove node from + remove node from its parent.
    6. If original end node is a {{Text}}, @@ -8349,7 +8570,7 @@ the result of cloning the contents of
    7. If node's parent is not - null, remove node from its + null, remove node from its parent.