Skip to content

Commit 1c58d2c

Browse files
jakearchibaldannevk
authored andcommitted
Define AbortController and AbortSignal classes
New APIs for a generic reusable abort mechanism. Both Fetch and Streams will build new features on this. Tests: web-platform-tests/wpt#5960. Fixes part of #438. (This commit is also authored by Jake and Anne.)
1 parent 912d587 commit 1c58d2c

File tree

1 file changed

+241
-19
lines changed

1 file changed

+241
-19
lines changed

dom.bs

Lines changed: 241 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ No Editor: true
1313
!Tests: <a href=https://github.com/w3c/web-platform-tests/tree/master/dom>web-platform-tests dom/</a> (<a href=https://github.com/w3c/web-platform-tests/labels/dom>ongoing work</a>)
1414
!Translation (non-normative): <span title=Japanese><a href=https://triple-underscore.github.io/DOM4-ja.html lang=ja hreflang=ja rel=alternate>日本語</a></span>
1515
Logo: https://resources.whatwg.org/logo-dom.svg
16-
Abstract: DOM defines a platform-neutral model for events and node trees.
16+
Abstract: DOM defines a platform-neutral model for events, aborting activities, and node trees.
1717
Ignored Terms: EmptyString, Array, Document
1818
Boilerplate: omit feedback-header, omit conformance
1919
</pre>
@@ -562,7 +562,7 @@ algorithm below.
562562
the operation that caused <var>event</var> to be <a>dispatched</a> that it needs to be canceled.
563563

564564
<dt><code><var>event</var> . {{Event/defaultPrevented}}</code>
565-
<dd>Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancellation,
565+
<dd>Returns true if {{Event/preventDefault()}} was invoked successfully to indicate cancelation,
566566
and false otherwise.
567567

568568
<dt><code><var>event</var> . {{Event/composed}}</code>
@@ -1445,6 +1445,228 @@ can only be used to influence an ongoing one.
14451445

14461446

14471447

1448+
<h2 id=aborting-ongoing-activities>Aborting ongoing activities</h3>
1449+
1450+
<p>Though promises do not have a built-in aborting mechanism, many APIs using them require abort
1451+
semantics. {{AbortController}} is meant to support these requirements by providing an
1452+
{{AbortController/abort()}} method that toggles the state of a corresponding {{AbortSignal}} object.
1453+
The API which wishes to support aborting can accept an {{AbortSignal}} object, and use its state to
1454+
determine how to proceed.
1455+
1456+
<p>APIs that rely upon {{AbortController}} are encouraged to respond to {{AbortController/abort()}}
1457+
by rejecting any unsettled promise with a new {{DOMException}} with [=error name=] "{{AbortError}}".
1458+
1459+
<div class=example id=aborting-ongoing-activities-example>
1460+
<p>A hypothetical <code>doAmazingness({ ... })</code> method could accept an {{AbortSignal}} object
1461+
in order to support aborting as follows:
1462+
1463+
<pre><code class=lang-javascript>
1464+
const controller = new AbortController();
1465+
const signal = controller.signal;
1466+
1467+
startSpinner();
1468+
1469+
doAmazingness({ ..., signal })
1470+
.then(result => ...)
1471+
.catch(err => {
1472+
if (err.name == 'AbortError') return;
1473+
showUserErrorMessage();
1474+
})
1475+
.then(() => stopSpinner());
1476+
1477+
// &hellip;
1478+
1479+
controller.abort();</code></pre>
1480+
1481+
<p><code>doAmazingness</code> could be implemented as follows:
1482+
1483+
<pre><code class=lang-javascript>
1484+
function doAmazingness({signal}) {
1485+
return new Promise((resolve, reject) => {
1486+
// Begin doing amazingness, and call resolve(result) when done.
1487+
// But also, watch for signals:
1488+
signal.addEventListener('abort', () => {
1489+
// Stop doing amazingness, and:
1490+
reject(new DOMException('Aborted', 'AbortError'));
1491+
});
1492+
});
1493+
}
1494+
</code></pre>
1495+
1496+
<p>APIs that require more granular control could extend both {{AbortController}} and
1497+
{{AbortSignal}} objects according to their needs.
1498+
</div>
1499+
1500+
1501+
<h3 id=interface-abortcontroller>Interface {{AbortController}}</h3>
1502+
1503+
<pre class="idl">
1504+
[Constructor,
1505+
Exposed=(Window,Worker)]
1506+
interface AbortController {
1507+
[SameObject] readonly attribute AbortSignal signal;
1508+
1509+
void abort();
1510+
};</pre>
1511+
1512+
<dl class=domintro>
1513+
<dt><code><var>controller</var> = new <a constructor lt=AbortController()>AbortController</a>()</code>
1514+
<dd>Returns a new <var>controller</var> whose {{AbortController/signal}} is set to a newly
1515+
created {{AbortSignal}} object.
1516+
1517+
<dt><code><var>controller</var> . <a attribute for=AbortController>signal</a></code>
1518+
<dd>Returns the {{AbortSignal}} object associated with this object.
1519+
1520+
<dt><code><var>controller</var> . <a method for=AbortController lt=abort()>abort</a>()</code>
1521+
<dd>Invoking this method will set this object's {{AbortSignal}}'s [=AbortSignal/aborted flag=] and
1522+
signal to any observers that the associated activity is to be aborted.
1523+
</dl>
1524+
1525+
<p>An {{AbortController}} object has an associated <dfn for=AbortController>signal</dfn> (an
1526+
{{AbortSignal}} object).
1527+
1528+
<p>The <dfn constructor for=AbortController><code>AbortController()</code></dfn> constructor, when
1529+
invoked, must run these steps:
1530+
1531+
<ol>
1532+
<li><p>Let <var>signal</var> be a new {{AbortSignal}} object.
1533+
1534+
<li><p>Let <var>controller</var> be a new {{AbortController}} object whose
1535+
<a for=AbortController>signal</a> is <var>signal</var>.
1536+
1537+
<li><p>Return <var>controller</var>.
1538+
</ol>
1539+
1540+
<p>The <dfn attribute for=AbortController><code>signal</code></dfn> attribute's getter must return
1541+
<a>context object</a>'s <a for=AbortController>signal</a>.
1542+
1543+
<p>The <dfn method for=AbortController><code>abort()</code></dfn> method, when invoked, must
1544+
<a for=AbortSignal>signal abort</a> on <a>context object</a>'s <a for=AbortController>signal</a>.
1545+
1546+
1547+
<h3 id=interface-AbortSignal>Interface {{AbortSignal}}</h3>
1548+
1549+
<pre class="idl">
1550+
[Exposed=(Window,Worker)]
1551+
interface AbortSignal : EventTarget {
1552+
readonly attribute boolean aborted;
1553+
1554+
attribute EventHandler onabort;
1555+
};</pre>
1556+
1557+
<dl class=domintro>
1558+
<dt><code><var>signal</var> . <a attribute for=AbortSignal>aborted</a></code>
1559+
<dd>Returns true if this {{AbortSignal}}'s {{AbortController}} has signaled to abort, and false
1560+
otherwise.
1561+
</dl>
1562+
1563+
<p>An {{AbortSignal}} object has an associated <dfn for=AbortSignal>aborted flag</dfn>. It is unset
1564+
unless specified otherwise.
1565+
1566+
<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>abort algorithms</dfn>, which is a
1567+
<a for=/>set</a> of algorithms which are to be executed when its [=AbortSignal/aborted flag=] is
1568+
set. Unless specified otherwise, its value is the empty set.
1569+
1570+
<p>To <dfn export for=AbortSignal>add</dfn> an algorithm <var>algorithm</var> to an {{AbortSignal}}
1571+
object <var>signal</var>, run these steps:
1572+
1573+
<ol>
1574+
<li><p>If <var>signal</var>'s <a for=AbortSignal>aborted flag</a> is set, then return.
1575+
1576+
<li><p><a for=set>Append</a> <var>algorithm</var> to <var>signal</var>'s
1577+
<a for=AbortSignal>abort algorithms</a>.
1578+
</ol>
1579+
1580+
<p>To <dfn export for=AbortSignal>remove</dfn> an algorithm <var>algorithm</var> from an
1581+
{{AbortSignal}} <var>signal</var>, <a for=set>remove</a> <var>algorithm</var> from
1582+
<var>signal</var>'s <a for=AbortSignal>abort algorithms</a>.
1583+
1584+
<p class="note no-backref">The [=AbortSignal/abort algorithms=] enable APIs with complex
1585+
requirements to react in a reasonable way to {{AbortController/abort()}}. For example, a given API's
1586+
[=AbortSignal/aborted flag=] might need to be propagated to a cross-thread environment, such as a
1587+
service worker.
1588+
1589+
<p>The <dfn attribute for=AbortSignal>aborted</dfn> attribute's getter must return true if
1590+
<a>context object</a>'s [=AbortSignal/aborted flag=] is set, and false otherwise.
1591+
1592+
<p class=note>Changes to an {{AbortSignal}} object represent the wishes of the corresponding
1593+
{{AbortController}} object, but an API observing the {{AbortSignal}} object can chose to ignore
1594+
them. For instance, if the operation has already completed.
1595+
1596+
<p>To <dfn export for=AbortSignal>signal abort</dfn>, given a {{AbortSignal}} object
1597+
<var>signal</var>, run these steps:
1598+
1599+
<ol>
1600+
<li><p>If <var>signal</var>'s [=AbortSignal/aborted flag=] is set, then return.
1601+
1602+
<li><p>Set <var>signal</var>'s [=AbortSignal/aborted flag=].
1603+
1604+
<li><p><a for=set>For each</a> <var>algorithm</var> in <var>signal</var>'s
1605+
[=AbortSignal/abort algorithms=]: run <var>algorithm</var>.
1606+
1607+
<li><p><a for=set>Empty</a> <var>signal</var>'s <a for=AbortSignal>abort algorithms</a>.
1608+
1609+
<li><p>[=Fire an event=] named <code event for=AbortSignal>abort</code> at <var>signal</var>.
1610+
</ol>
1611+
1612+
1613+
<h3 id=abortcontroller-api-integration>Using {{AbortController}} and {{AbortSignal}} objects in
1614+
APIs</h3>
1615+
1616+
<p>Any web platform API using promises to represent operations that can be aborted must adhere to
1617+
the following:
1618+
1619+
<ul class=brief>
1620+
<li>Accept {{AbortSignal}} objects through a <code>signal</code> dictionary member.
1621+
<li>Convey that the operation got aborted by rejecting the promise with an "{{AbortError}}"
1622+
{{DOMException}}.
1623+
<li>Reject immediately if the {{AbortSignal}}'s [=AbortSignal/aborted flag=] is already set,
1624+
otherwise:
1625+
<li>Use the [=AbortSignal/abort algorithms=] mechanism to observe changes to the {{AbortSignal}}
1626+
object and do so in a manner that does not lead to clashes with other observers.
1627+
</ul>
1628+
1629+
<div class=example id=aborting-ongoing-activities-spec-example>
1630+
<p>The steps for a promise-returning method <code>doAmazingness(options)</code> could be as
1631+
follows:
1632+
1633+
<ol>
1634+
<li><p>Let |p| be [=a new promise=].
1635+
1636+
<li>
1637+
<p>If |options|' <code>signal</code> member is present, then:
1638+
1639+
<ol>
1640+
<li><p>If |options|' <code>signal</code>'s [=AbortSignal/aborted flag=] is set, then [=reject=]
1641+
|p| with an "{{AbortError}}" {{DOMException}} and return |p|.
1642+
1643+
<li>
1644+
<p>[=AbortSignal/Add|Add the following abort steps=] to |options|' <code>signal</code>:
1645+
1646+
<ol>
1647+
<li><p>Stop doing amazing things.
1648+
1649+
<li><p>[=Reject=] |p| with an "{{AbortError}}" {{DOMException}}.
1650+
</ol>
1651+
</ol>
1652+
1653+
<li>
1654+
<p>Run these steps [=in parallel=]:
1655+
1656+
<ol>
1657+
<li><p>Let |amazingResult| be the result of doing some amazing things.
1658+
1659+
<li><p>[=Resolve=] |p| with |amazingResult|.
1660+
</ol>
1661+
1662+
<li><p>Return |p|.
1663+
</ol>
1664+
</div>
1665+
1666+
<p>APIs not using promises should still adhere to the above as much as possible.
1667+
1668+
1669+
14481670
<h2 id=nodes>Nodes</h2>
14491671

14501672
<h3 id=introduction-to-the-dom>Introduction to "The DOM"</h3>
@@ -1963,7 +2185,7 @@ before a <var>child</var>, with an optional <i>suppress observers flag</i>, run
19632185

19642186
<li>If <var>node</var> is a {{DocumentFragment}}
19652187
<a>node</a>,
1966-
<a>remove</a> its
2188+
<a for=/>remove</a> its
19672189
<a>children</a> with the
19682190
<i>suppress observers flag</i> set.
19692191

@@ -2135,7 +2357,7 @@ within a <var>parent</var>, run these steps:
21352357
<ol>
21362358
<li><p>Set <var>removedNodes</var> to a list solely containing <var>child</var>.
21372359

2138-
<li><p><a>Remove</a> <var>child</var> from its <var>parent</var> with the
2360+
<li><p><a for=/>Remove</a> <var>child</var> from its <var>parent</var> with the
21392361
<i>suppress observers flag</i> set.
21402362
</ol>
21412363

@@ -2176,7 +2398,7 @@ To <dfn export for=Node id=concept-node-replace-all>replace all</dfn> with a
21762398
<a>node</a>, and a list containing <var>node</var>
21772399
otherwise.
21782400

2179-
<li><a>Remove</a> all
2401+
<li><a for=/>Remove</a> all
21802402
<var>parent</var>'s <a>children</a>, in
21812403
<a>tree order</a>, with the
21822404
<i>suppress observers flag</i> set.
@@ -2201,8 +2423,7 @@ To <dfn export id=concept-node-pre-remove>pre-remove</dfn> a <var>child</var> fr
22012423
<li>If <var>child</var>'s <a for=tree>parent</a> is not <var>parent</var>, then <a>throw</a> a
22022424
{{NotFoundError}}.
22032425

2204-
<li><a>Remove</a> <var>child</var>
2205-
from <var>parent</var>.
2426+
<li><a for=/>Remove</a> <var>child</var> from <var>parent</var>.
22062427

22072428
<li>Return <var>child</var>.
22082429
<!-- technically this is post-remove -->
@@ -2212,7 +2433,7 @@ To <dfn export id=concept-node-pre-remove>pre-remove</dfn> a <var>child</var> fr
22122433
<p><a lt="Other applicable specifications">Specifications</a> may define
22132434
<dfn export id=concept-node-remove-ext>removing steps</dfn> for all or some <a>nodes</a>. The
22142435
algorithm is passed <var ignore>removedNode</var>, and optionally <var ignore>oldParent</var>, as
2215-
indicated in the <a>remove</a> algorithm below.
2436+
indicated in the <a for=/>remove</a> algorithm below.
22162437

22172438
<p>To <dfn export id=concept-node-remove>remove</dfn> a <var>node</var> from a <var>parent</var>,
22182439
with an optional <i>suppress observers flag</i>, run these steps:
@@ -2683,7 +2904,7 @@ steps:
26832904
<ol>
26842905
<li><p>If <a>context object</a>'s <a for=tree>parent</a> is null, then return.
26852906

2686-
<li><p><a>Remove</a> the <a>context object</a> from <a>context object</a>'s
2907+
<li><p><a for=/>Remove</a> the <a>context object</a> from <a>context object</a>'s
26872908
<a for=tree>parent</a>.
26882909
</ol>
26892910

@@ -3842,8 +4063,8 @@ steps for each <a>descendant</a> <a>exclusive <code>Text</code> node</a> <var>no
38424063
<ol>
38434064
<li>Let <var>length</var> be <var>node</var>'s <a for=Node>length</a>.
38444065

3845-
<li>If <var>length</var> is zero, then <a>remove</a> <var>node</var> and continue with the next
3846-
<a>exclusive <code>Text</code> node</a>, if any.
4066+
<li>If <var>length</var> is zero, then <a for=/>remove</a> <var>node</var> and continue with the
4067+
next <a>exclusive <code>Text</code> node</a>, if any.
38474068

38484069
<li>Let <var>data</var> be the concatenation of the <a for=CharacterData>data</a> of
38494070
<var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a> (excluding itself), in
@@ -3881,8 +4102,8 @@ steps for each <a>descendant</a> <a>exclusive <code>Text</code> node</a> <var>no
38814102
<li><p>Set <var>currentNode</var> to its <a for=tree>next sibling</a>.
38824103
</ol>
38834104

3884-
<li><a>Remove</a> <var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a> (excluding
3885-
itself), in <a>tree order</a>.
4105+
<li><a for=/>Remove</a> <var>node</var>'s <a>contiguous exclusive <code>Text</code> nodes</a>
4106+
(excluding itself), in <a>tree order</a>.
38864107
</ol>
38874108

38884109
<p class="note">{{Node/normalize()}} does not need to run any
@@ -4987,8 +5208,8 @@ these steps:
49875208
<ol>
49885209
<li><p>Let <var>oldDocument</var> be <var>node</var>'s <a for=Node>node document</a>.
49895210

4990-
<li><p>If <var>node</var>'s <a for=tree>parent</a> is not null, <a>remove</a> <var>node</var> from its
4991-
<a for=tree>parent</a>.
5211+
<li><p>If <var>node</var>'s <a for=tree>parent</a> is not null, <a for=/>remove</a> <var>node</var>
5212+
from its <a for=tree>parent</a>.
49925213

49935214
<li>
49945215
<p>If <var>document</var> is not <var>oldDocument</var>, then:
@@ -7159,7 +7380,7 @@ might itself be modified as part of the mutation to the
71597380
<a>node tree</a> when e.g. part of the content
71607381
it represents is mutated.
71617382

7162-
<p class="note no-backref">See the <a>insert</a> and <a>remove</a> algorithms, the
7383+
<p class="note no-backref">See the <a>insert</a> and <a for=/>remove</a> algorithms, the
71637384
{{Node/normalize()}} method, and the <a>replace data</a> and <a lt="split a Text node">split</a>
71647385
algorithms for the hairy details.
71657386

@@ -7228,7 +7449,7 @@ the <a>boundary point</a>'s
72287449
<a>length</a>, inclusive. Algorithms that
72297450
modify a <a>tree</a> (in particular the
72307451
<a>insert</a>,
7231-
<a>remove</a>,
7452+
<a for=/>remove</a>,
72327453
<a>replace data</a>, and
72337454
<a lt="split a Text node">split</a> algorithms) also modify
72347455
<a>ranges</a> associated with that
@@ -7816,7 +8037,7 @@ run these steps:
78168037

78178038
<li>For each <var>node</var> in <var>nodes to remove</var>,
78188039
in <a>tree order</a>,
7819-
<a>remove</a> <var>node</var> from
8040+
<a for=/>remove</a> <var>node</var> from
78208041
its <a for=tree>parent</a>.
78218042

78228043
<li>If <var>original end node</var> is a {{Text}},
@@ -8384,7 +8605,7 @@ the result of <a lt="clone the contents of a range">cloning the contents</a> of
83848605
<!-- Because we're about to remove node from its parent. -->
83858606

83868607
<li>If <var>node</var>'s <a for=tree>parent</a> is not
8387-
null, <a>remove</a> <var>node</var> from its
8608+
null, <a for=/>remove</a> <var>node</var> from its
83888609
<a for=tree>parent</a>.
83898610

83908611
<!-- Browsers disagree on how to handle the case where the range is
@@ -9811,6 +10032,7 @@ Mounir Lamouri,
981110032
Michael™ Smith,
981210033
Mike Champion,
981310034
Mike Taylor,
10035+
Mike West,
981410036
Ojan Vafai,
981510037
Oliver Nightingale,
981610038
Olli Pettay,

0 commit comments

Comments
 (0)