-
Notifications
You must be signed in to change notification settings - Fork 317
Add AbortSignal.any() #1152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AbortSignal.any() #1152
Changes from 7 commits
6c3a8ca
d7b8f7f
639183b
8d4a5ea
d8775bb
67144aa
d9cae6e
707823a
d74bc26
4e2146c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ type: interface | |
urlPrefix: https://tc39.es/ecma262/#; spec: ECMASCRIPT | ||
text: Construct; url: sec-construct; type: abstract-op | ||
type: dfn | ||
text: current realm; url: current-realm | ||
text: realm; url: realm | ||
text: surrounding agent; url: surrounding-agent | ||
urlPrefix: https://w3c.github.io/hr-time/#; spec: HR-TIME | ||
|
@@ -1782,8 +1783,11 @@ constructor steps are: | |
<a>this</a>'s <a for=AbortController>signal</a>. | ||
|
||
<p>The <dfn method for=AbortController><code>abort(<var>reason</var>)</code></dfn> method steps are | ||
to <a for=AbortSignal>signal abort</a> on <a>this</a>'s <a for=AbortController>signal</a> with | ||
<var>reason</var> if it is given. | ||
to <a for=AbortController>signal abort</a> on <a>this</a> with <var>reason</var> if it is given. | ||
|
||
<p>To <dfn export for=AbortController>signal abort</dfn> on an {{AbortController}} <var>controller</var> | ||
with an optional <var>reason</var>, <a for=AbortSignal>signal abort</a> on <var>controller</var>'s | ||
<a for=AbortController>signal</a> with <var>reason</var> if it is given. | ||
|
||
<h3 id=interface-AbortSignal>Interface {{AbortSignal}}</h3> | ||
|
||
|
@@ -1792,6 +1796,7 @@ to <a for=AbortSignal>signal abort</a> on <a>this</a>'s <a for=AbortController>s | |
interface AbortSignal : EventTarget { | ||
[NewObject] static AbortSignal abort(optional any reason); | ||
[Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); | ||
[NewObject] static AbortSignal _any(sequence<AbortSignal> signals); | ||
|
||
readonly attribute boolean aborted; | ||
readonly attribute any reason; | ||
|
@@ -1805,6 +1810,11 @@ interface AbortSignal : EventTarget { | |
<dd>Returns an {{AbortSignal}} instance whose <a for=AbortSignal>abort reason</a> is set to | ||
<var>reason</var> if not undefined; otherwise to an "{{AbortError!!exception}}" {{DOMException}}. | ||
|
||
<dt><code>AbortSignal . <a method for=AbortSignal lt=any(signals)>any</a>(<var>signals</var>)</code> | ||
<dd>Returns an {{AbortSignal}} instance which will be aborted once any of <var>signals</var> is | ||
aborted. Its <a for=AbortSignal>abort reason</a> will be set to whichever one of <var>signals</var> | ||
caused it to be aborted. | ||
|
||
<dt><code>AbortSignal . <a method for=AbortSignal lt=timeout(milliseconds)>timeout</a>(<var>milliseconds</var>)</code> | ||
<dd>Returns an {{AbortSignal}} instance which will be aborted in <var>milliseconds</var> | ||
milliseconds. Its <a for=AbortSignal>abort reason</a> will be set to a | ||
|
@@ -1821,35 +1831,31 @@ interface AbortSignal : EventTarget { | |
{{AbortController}} has signaled to abort; otherwise, does nothing. | ||
</dl> | ||
|
||
<p>An {{AbortSignal}} object has an associated <dfn export for=AbortSignal>abort reason</dfn>, which is a | ||
JavaScript value. It is undefined unless specified otherwise. | ||
|
||
<p>An {{AbortSignal}} object is <dfn export for="AbortSignal">aborted</dfn> when its | ||
[=AbortSignal/abort reason=] is not undefined. | ||
|
||
<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>abort algorithms</dfn>, which is a | ||
<a for=/>set</a> of algorithms which are to be executed when it is [=AbortSignal/aborted=]. Unless | ||
specified otherwise, its value is the empty set. | ||
|
||
<p>To <dfn export for=AbortSignal>add</dfn> an algorithm <var>algorithm</var> to an {{AbortSignal}} | ||
object <var>signal</var>, run these steps: | ||
|
||
<ol> | ||
<li><p>If <var>signal</var> is [=AbortSignal/aborted=], then return. | ||
|
||
<li><p><a for=set>Append</a> <var>algorithm</var> to <var>signal</var>'s | ||
<a for=AbortSignal>abort algorithms</a>. | ||
</ol> | ||
<p>An {{AbortSignal}} object has an associated <dfn export for=AbortSignal>abort reason</dfn> (a | ||
JavaScript value), which is initially undefined. | ||
|
||
<p>To <dfn export for=AbortSignal>remove</dfn> an algorithm <var>algorithm</var> from an | ||
{{AbortSignal}} <var>signal</var>, <a for=set>remove</a> <var>algorithm</var> from | ||
<var>signal</var>'s <a for=AbortSignal>abort algorithms</a>. | ||
<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>abort algorithms</dfn>, (a | ||
<a for=/>set</a> of algorithms which are to be executed when it is [=AbortSignal/aborted=]), | ||
shaseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
which is initially empty. | ||
|
||
<p class=note>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/abort reason=] might need to be propagated to a cross-thread environment, such as a | ||
service worker. | ||
|
||
<p>An {{AbortSignal}} object has a <dfn for="AbortSignal">composite</dfn> (a boolean), which is | ||
initially false. | ||
|
||
<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>source signals</dfn>, (a weak | ||
<a for=/>set</a> of {{AbortSignal}} objects that the object is dependent on for its | ||
shaseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[=AbortSignal/aborted=] state), which is initially empty. | ||
|
||
shaseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<p>An {{AbortSignal}} object has associated <dfn for=AbortSignal>dependent signals</dfn> (a weak | ||
<a for=/>set</a> of {{AbortSignal}} objects that are dependent on the object for their | ||
[=AbortSignal/aborted=] state), which is initially empty. | ||
|
||
<hr> | ||
|
||
<p>The static <dfn method for=AbortSignal><code>abort(<var>reason</var>)</code></dfn> method steps | ||
are: | ||
|
||
|
@@ -1887,6 +1893,10 @@ steps are: | |
<li><p>Return <var>signal</var>. | ||
</ol> | ||
|
||
<p>The static <dfn method for=AbortSignal><code>any(<var>signals</var>)</code></dfn> method | ||
steps are to return the result of <a>creating a composite abort signal</a> from <var>signals</var> | ||
using {{AbortSignal}} and the <a>current realm</a>. | ||
|
||
<p>The <dfn attribute for=AbortSignal>aborted</dfn> getter steps are to return true if <a>this</a> | ||
is [=AbortSignal/aborted=]; otherwise false. | ||
|
||
|
@@ -1924,48 +1934,109 @@ is [=AbortSignal/aborted=]; otherwise false. | |
<dfn event for=AbortSignal><code>abort</code></dfn>. | ||
|
||
<p class=note>Changes to an {{AbortSignal}} object represent the wishes of the corresponding | ||
{{AbortController}} object, but an API observing the {{AbortSignal}} object can chose to ignore | ||
{{AbortController}} object, but an API observing the {{AbortSignal}} object can choose to ignore | ||
them. For instance, if the operation has already completed. | ||
|
||
<p>To <dfn export for=AbortSignal>signal abort</dfn>, given an {{AbortSignal}} object | ||
<var>signal</var> and an optional <var>reason</var>, run these steps: | ||
<hr> | ||
|
||
<p>An {{AbortSignal}} object is <dfn export for="AbortSignal">aborted</dfn> when its | ||
[=AbortSignal/abort reason=] is not undefined. | ||
annevk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<p>To <dfn export for=AbortSignal>add</dfn> an algorithm <var>algorithm</var> to an {{AbortSignal}} | ||
object <var>signal</var>: | ||
|
||
<ol> | ||
<li><p>If <var>signal</var> is [=AbortSignal/aborted=], then return. | ||
|
||
<li><p><a for=set>Append</a> <var>algorithm</var> to <var>signal</var>'s | ||
<a for=AbortSignal>abort algorithms</a>. | ||
</ol> | ||
|
||
<p>To <dfn export for=AbortSignal>remove</dfn> an algorithm <var>algorithm</var> from an | ||
{{AbortSignal}} <var>signal</var>, <a for=set>remove</a> <var>algorithm</var> from | ||
<var>signal</var>'s <a for=AbortSignal>abort algorithms</a>. | ||
|
||
<p>To <dfn for=AbortSignal>signal abort</dfn>, given an {{AbortSignal}} object <var>signal</var> and | ||
an optional <var>reason</var>: | ||
|
||
<ol> | ||
<li><p>If <var>signal</var> is [=AbortSignal/aborted=], then return. | ||
|
||
<li><p>Set <var>signal</var>'s [=AbortSignal/abort reason=] to <var>reason</var> if it is given; | ||
otherwise to a new "{{AbortError!!exception}}" {{DOMException}}. | ||
|
||
<li><p><a for=set>For each</a> <var>algorithm</var> in <var>signal</var>'s | ||
<li><p><a for=set>For each</a> <var>algorithm</var> of <var>signal</var>'s | ||
[=AbortSignal/abort algorithms=]: run <var>algorithm</var>. | ||
|
||
<li><p><a for=set>Empty</a> <var>signal</var>'s <a for=AbortSignal>abort algorithms</a>. | ||
|
||
<li><p>[=Fire an event=] named {{AbortSignal/abort}} at <var>signal</var>. | ||
|
||
<li><p><a for=set>For each</a> <var>dependentSignal</var> of <var>signal</var>'s | ||
[=AbortSignal/dependent signals=], [=AbortSignal/signal abort=] on <var>dependentSignal</var> with | ||
<var>signal</var>'s [=AbortSignal/abort reason=]. | ||
</ol> | ||
|
||
<p>A <var>followingSignal</var> (an {{AbortSignal}}) is made to | ||
<dfn export for=AbortSignal>follow</dfn> a <var>parentSignal</var> (an {{AbortSignal}}) by running | ||
these steps: | ||
<p>To <dfn export>create a composite abort signal</dfn> from a list of {{AbortSignal}} objects | ||
<var>signals</var>, using <var>signalInterface</var>, which must be either {{AbortSignal}} or an | ||
interface that inherits from it, and a <var>realm</var>: | ||
|
||
<ol> | ||
<li><p>If <var>followingSignal</var> is [=AbortSignal/aborted=], then return. | ||
<li> | ||
<p>Let <var>resultSignal</var> be a <a for=/>new</a> object implementing | ||
<var>signalInterface</var> using <var>realm</var>. | ||
|
||
<li><p>If <var>parentSignal</var> is [=AbortSignal/aborted=], then | ||
<a for=AbortSignal>signal abort</a> on <var>followingSignal</var> with <var>parentSignal</var>'s | ||
[=AbortSignal/abort reason=]. | ||
<li><p>Set <var>resultSignal</var>'s [=AbortSignal/composite=] to true. | ||
shaseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
<li><p><a for=list>For each</a> <var>signal</var> of <var>signals</var>: if <var>signal</var> is | ||
[=AbortSignal/aborted=], then set <var>resultSignal</var>'s [=AbortSignal/abort reason=] to | ||
<var>signal</var>'s [=AbortSignal/abort reason=] and return <var>resultSignal</var>. | ||
|
||
<li> | ||
<p>Otherwise, <a for=AbortSignal lt=add>add the following abort steps</a> to | ||
<var>parentSignal</var>: | ||
<p><a for=list>For each</a> <var>signal</var> of <var>signals</var>: | ||
|
||
<ol> | ||
<li><p><a for=AbortSignal>Signal abort</a> on <var>followingSignal</var> with | ||
<var>parentSignal</var>'s [=AbortSignal/abort reason=]. | ||
<li> | ||
<p>If <var>signal</var>'s [=AbortSignal/composite=] is false, then: | ||
|
||
<ol> | ||
<li><p><a for=set>Append</a> <var>signal</var> to <var>resultSignal</var>'s | ||
[=AbortSignal/source signals=]. | ||
|
||
<li><p><a for=set>Append</a> <var>resultSignal</var> to <var>signal</var>'s | ||
[=AbortSignal/dependent signals=]. | ||
</ol> | ||
|
||
<li> | ||
<p>Otherwise, <a for=list>for each</a> <var>sourceSignal</var> of <var>signal</var>'s | ||
[=AbortSignal/source signals=]: | ||
|
||
<ol> | ||
<li><p>Assert: <var>sourceSignal</var> is not [=AbortSignal/aborted=] and not | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not clear to me what ensure that the second part of the assertion holds. How do we know for sure that AbortSignal.any() cannot be called with an AbortSignal parameter that has it's Maybe we should recursively go through source signals? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be called with dependent signals. When it encounters one in the outer for-loop, it iterates over the dependent signal's source signals and links those. And by always linking to a dependent's source signals, dependent signals are never added as sources. So this is saying "a dependent signal's source signal cannot be a dependent signal." The idea is something like this: const root = someController.signal;
// Non-dependent signal passed, simple case (append it directly):
// root.__dependent_signals = [signal1];
// signal1.__source_signals = [root];
const signal1 = AbortSignal.any([someController.signal]);
// Dependent signal passed, iterate over its source signals and link those:
// root.__dependent_signals = [signal1, signal2];
// signal2.__source_signals = [root]; (note: root is linked, not signal1).
const signal2 = AbortSignal.any([signal1]);
// Same as above:
// root.__dependent_signals = [signal1, signal2, signal3];
// signal3.__source_signals = [root];
const signal3 = AbortSignal.any([signal2]); This is by design to "flatten" the signal dependency graph by linking directly to signals that can abort directly (timeouts or controller signals), which helps optimize GC since intermediates aren't kept alive to propagate abort. (Note also that new sources are fixed at construction time). |
||
[=AbortSignal/composite=]. | ||
|
||
<li><p>If <var>resultSignal</var>'s [=AbortSignal/source signals=] <a for=set>contains</a> | ||
<var>sourceSignal</var>, then <a for=iteration>continue</a>. | ||
|
||
<li><p><a for=set>Append</a> <var>sourceSignal</var> to <var>resultSignal</var>'s | ||
[=AbortSignal/source signals=]. | ||
|
||
<li><p><a for=set>Append</a> <var>resultSignal</var> to <var>sourceSignal</var>'s | ||
[=AbortSignal/dependent signals=]. | ||
</ol> | ||
</ol> | ||
|
||
<li>Return <var>resultSignal</var>. | ||
shaseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</ol> | ||
|
||
|
||
<h4 id=abort-signal-garbage-collection>Garbage collection</h4> | ||
|
||
<p>A non-[=AbortSignal/aborted=] [=AbortSignal/composite=] {{AbortSignal}} object must not be | ||
garbage collected while its [=AbortSignal/source signals=] is non-empty and it has registered event | ||
listeners for its {{AbortSignal/abort}} event or its [=AbortSignal/abort algorithms=] is non-empty. | ||
|
||
|
||
<h3 id=abortcontroller-api-integration>Using {{AbortController}} and {{AbortSignal}} objects in | ||
APIs</h3> | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.