Skip to content
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 async iterable<T> type to WebIDL #1397

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
14f1bdb
Add `async iterable<T>` type to WebIDL
lucacasonato Mar 25, 2024
b7df167
Reason argument for close async iterable
lucacasonato Mar 26, 2024
0402e85
fix typo
lucacasonato Mar 26, 2024
b6f7392
clarify the details of allowed positions for async iterables
lucacasonato Mar 28, 2024
6363f6c
fix ci
lucacasonato May 7, 2024
5533589
iterable -> iterable + iterator
lucacasonato Jul 18, 2024
6559e06
fixes
lucacasonato Jul 18, 2024
3789832
Merge remote-tracking branch 'origin/main' into async_iterator_type
lucacasonato Jul 18, 2024
6f284c1
fix
lucacasonato Jul 18, 2024
dcbe738
fix
lucacasonato Jul 23, 2024
bd86c51
fix
lucacasonato Jul 23, 2024
f97033b
Fix typo
MattiasBuelens Jul 25, 2024
30fb12d
Fix link text
MattiasBuelens Jul 25, 2024
3469138
Fix duplicate dfn
MattiasBuelens Jul 25, 2024
947fb78
Fix link id
MattiasBuelens Jul 25, 2024
118b446
Simplify
MattiasBuelens Jul 25, 2024
ca5967b
Merge pull request #1 from MattiasBuelens/async_iterator_type
lucacasonato Jul 29, 2024
11f9a9f
review comments
lucacasonato Jul 30, 2024
dc07972
fix
lucacasonato Jul 30, 2024
dd56f97
an
lucacasonato Jul 30, 2024
9a79b42
fixes
lucacasonato Aug 2, 2024
6374863
review comments
lucacasonato Aug 5, 2024
70e9646
wip
lucacasonato Jan 24, 2025
3c5164a
Merge remote-tracking branch 'origin/main' into async_iterator_type
lucacasonato Jan 24, 2025
df3ec8d
fix up overload resolution + union for string object special case
lucacasonato Jan 24, 2025
5a0bb8e
[=Iterator Record=]
lucacasonato Jan 24, 2025
df8383f
close an async iterator |reason|
lucacasonato Jan 24, 2025
1b6489e
Update index.bs
lucacasonato Jan 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 237 additions & 6 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ urlPrefix: https://tc39.es/ecma262/; spec: ecmascript
type: argument
text: NewTarget; url: sec-built-in-function-objects
type: abstract-op
text: CreateAsyncFromSyncIterator; url: sec-createasyncfromsynciterator
text: Completion; url: sec-completion-record-specification-type
text: IsInteger; url: sec-isinteger
text: Type; url: sec-ecmascript-data-types-and-values
Expand Down Expand Up @@ -68,6 +69,7 @@ urlPrefix: https://tc39.es/ecma262/; spec: ecmascript
text: internal slot
text: own property; url: sec-own-property
text: PromiseCapability; url: sec-promisecapability-records
text: Iterator; url: sec-iterator-records
text: element size; url: table-the-typedarray-constructors
urlPrefix: https://tc39.es/proposal-resizablearraybuffer/; spec: RESIZABLE-BUFFERS-PROPOSAL
type: abstract-op
Expand Down Expand Up @@ -1671,6 +1673,7 @@ The type of the attribute, after resolving typedefs, must not be a
[=nullable type|nullable=] or non-nullable version of any of the following types:

* a [=sequence type=]
* an [=async iterable type=]
* a [=dictionary type=]
* a [=record type=]
* a [=union type=]
Expand Down Expand Up @@ -3360,6 +3363,7 @@ the following algorithm returns <i>true</i>.
<th><div><span>interface-like</span></div>
<th><div><span>callback function</span></div>
<th><div><span>dictionary-like</span></div>
<th><div><span>async iterable</span></div>
<th><div><span>sequence-like</span></div>
</thead>
<tr>
Expand All @@ -3375,6 +3379,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>
<td>●
<td>●
<tr>
<th>boolean
<td class="belowdiagonal">
Expand All @@ -3388,6 +3393,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>numeric types
<td class="belowdiagonal">
Expand All @@ -3401,6 +3407,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>bigint
<td class="belowdiagonal">
Expand All @@ -3414,6 +3421,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>string types
<td class="belowdiagonal">
Expand All @@ -3427,6 +3435,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>object
<td class="belowdiagonal">
Expand All @@ -3440,6 +3449,7 @@ the following algorithm returns <i>true</i>.
<td>
<td>
<td>
<td>
<tr>
<th>symbol
<td class="belowdiagonal">
Expand All @@ -3453,6 +3463,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>interface-like
<td class="belowdiagonal">
Expand All @@ -3466,6 +3477,7 @@ the following algorithm returns <i>true</i>.
<td>●
<td>●
<td>●
<td>●
<tr>
<th>callback function
<td class="belowdiagonal">
Expand All @@ -3479,6 +3491,7 @@ the following algorithm returns <i>true</i>.
<td>
<td>(c)
<td>●
<td>●
<tr>
<th>dictionary-like
<td class="belowdiagonal">
Expand All @@ -3492,6 +3505,21 @@ the following algorithm returns <i>true</i>.
<td class="belowdiagonal">
<td>
<td>●
<td>●
<tr>
<th>async iterable
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td>
<td>
<tr>
<th>sequence-like
<td class="belowdiagonal">
Expand All @@ -3504,6 +3532,7 @@ the following algorithm returns <i>true</i>.
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td class="belowdiagonal">
<td>
</table>

Expand Down Expand Up @@ -4069,7 +4098,7 @@ The following extended attributes are applicable to [=iterable declarations=]:
</pre>


<h4 id="idl-async-iterable">Asynchronously iterable declarations</h4>
<h4 oldids="idl-async-iterable" id="idl-async-iterable-declaration">Asynchronously iterable declarations</h4>

An [=interface=] can be declared to be asynchronously iterable by using an
<dfn id="dfn-async-iterable-declaration" export>asynchronously iterable declaration</dfn>
Expand Down Expand Up @@ -5676,6 +5705,7 @@ are known as <dfn id="dfn-object-type" export>object types</dfn>.
StringType Null
identifier Null
"sequence" "&lt;" TypeWithExtendedAttributes "&gt;" Null
"async iterable" "&lt;" TypeWithExtendedAttributes "&gt;" Null
"object" Null
"symbol" Null
BufferRelatedType Null
Expand Down Expand Up @@ -6177,6 +6207,34 @@ sequence is used.
Any [=list=] can be implicitly treated as a <code>sequence&lt;|T|&gt;</code>, as long as it contains
only [=list/items=] that are of type |T|.

<!-- Note: if we ever add synchronous iterable types, we should add a note here about why sequences and iterables are not the same. -->

<h4 id="idl-async-iterable-type" lt="async iterable" dfn export>Async iterable types — async iterable&lt;|T|&gt;</h4>

The <dfn lt="async iterable type" export>async iterable type</dfn> is a parameterized
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
type whose values are references to objects that can produce an asynchronously iterable, possibly infinite,
sequence of values of type |T|.

Unlike [=sequences=], which are fixed-length lists where all values are known in advance, the asynchronously
iterable sequences created by async iterables are lazy. Their values may be produced asynchronously
only during iteration, and thus the values or length might not be known at the time the async
iterable is created.

Async iterables are passed by reference in language bindings where they are represented by an object.
This means that passing an async iterable to a [=platform object=] will result in a reference to the
async iterable being kept by that object. Similarly, any async iterable returned from a platform
object will be a reference to the same object and modifications made to it will be visible to the
platform object. This is in contrast to sequences, which are always passed by value.

Note: Async iterables cannot be constructed from IDL. If returned from an operation, or used as the
type of a dictionary member, the async iterable will have originated from the host environment and
have been turned into an IDL type via a language binding. Instead of returning an async iterable
from an IDL operation, the operation might want to return an [=interface=] that has an
[=asynchronously iterable declaration=].

Async iterables must not be used as the type of an [=attribute=] or [=constant=].

There is no way to represent an async iterable value in IDL.

<h4 id="idl-record" lt="record" dfn export>Record types — record&lt;|K|, |V|&gt;</h4>

Expand Down Expand Up @@ -8115,6 +8173,162 @@ JavaScript Array values.
</div>


<h4 id="js-async-iterable">Async iterable — async iterable&lt;|T|&gt;</h4>
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved

In the JavaScript binding, IDL [=async iterable type|async iterable=] values are represented by
a [=struct=] with the following [=struct/items=]:

* <dfn for="JS async iterable">object</dfn>, a JavaScript value
* <dfn for="JS async iterable">method</dfn>, a JavaScript value
* <dfn for="JS async iterable">type</dfn>, either "<code>sync</code>" or "<code>async</code>"


<div id="js-to-async-iterable" algorithm="convert a JavaScript value to async iterable">
A JavaScript value |V| is [=converted to an IDL value|converted=]
to an IDL <a lt="async iterable">async iterable&lt;<var ignore>T</var>&gt;</a> value as follows:

1. If <a abstract-op>Type</a>(|V|) is not Object,
[=JavaScript/throw=] a <l spec=ecmascript>{{TypeError}}</l>.
1. Let |method| be [=?=] <a abstract-op>GetMethod</a>(obj, {{%Symbol.asyncIterator%}}).
1. If |method| is undefined:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For consistency with elsewhere (including within this PR), I guess we should use <emu-val>undefined</emu-val> here and below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. If |method| is undefined:
1. If |method| is <emu-val>undefined</emu-val>:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note you're still missing the instance two lines below.

1. Set |syncMethod| to [=?=] <a abstract-op>GetMethod</a>(obj, {{%Symbol.iterator%}}).
1. If |syncMethod| is undefined, [=JavaScript/throw=] a <l spec=ecmascript>{{TypeError}}</l>.
1. Return an IDL [=async iterable=] value with [=JS async iterable/object=] set to |V|,
[=JS async iterable/method=] set to |syncMethod|, and [=JS async iterable/type=] set to
"<code>sync</code>".
1. Return an IDL [=async iterable=] value with [=JS async iterable/object=] set to |V|,
[=JS async iterable/method=] set to |method|, and [=JS async iterable/type=] set to
"<code>async</code>".
</div>

<div id="async-iterable-to-js" algorithm="convert an async iterable to a JavaScript value">
An IDL <a lt="async iterable">async iterable&lt;<var ignore>T</var>&gt;</a> value |V| is
[=converted to a JavaScript value|converted=] to a JavaScript object as follows:

1. Return |V|'s [=JS async iterable/object=].
</div>

<h5 id="js-async-iterator-iteration">Iterating async iterators</h5>

An [=async iterable=] is not directly iterated over. Instead, it is first opened to create
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
a [=async iterator=]. The [=async iterator=] can be asynchronously iterated over to produce values.
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved

<dfn lt="async iterator" export>Async iterators</dfn> are [=structs=] with the following [=struct/items=]:
* <dfn for="JS async iterator">underlying record</dfn>, an [=Iterator=] record
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
* <dfn for="JS async iterator">type parameter</dfn>, an IDL type representing the type of values produced by the async iterator

<div algorithm>

To <dfn id="async-iterable-open" export lt="open an async iterable">open</dfn> an
<code><a lt="async iterable type">async iterable&lt;<var>T</var>&gt;</a></code> |iterable|:
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved

1. Let |iterator| be [=?=] <a abstract-op>GetIteratorFromMethod</a>(|iterable|'s
[=JS async iterable/object=], |iterable|'s [=JS async iterable/method=]).
1. If |iterable|'s [=JS async iterable/type=] is "<code>sync</code>", set |iterator| to
<a abstract-op>CreateAsyncFromSyncIterator</a>(|iterator|).
1. Return an [=async iterator=] value with [=JS async iterator/underlying record=] set to |iterator| and
[=JS async iterator/type parameter=] set to |T|.

</div>



<div algorithm>

To <dfn id="async-iterator-get-next-value" export lt="get an async iterator next value">get the next value</dfn> of an
[=async iterator=] |iterator|:

1. Let |nextResult| be
<a abstract-op>IteratorNext</a>(|iterator|'s [=JS async iterator/underlying record=]).
1. If |nextResult| is an abrupt completion, return [=a promise rejected with=]
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
|nextResult|.\[[Value]].
1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]].
1. Return the result of [=reacting=] to |nextPromise| with the following fulfillment
steps, given |iterResult|:
1. If <a abstract-op>Type</a>(|iterResult|) is not Object, [=JavaScript/throw=] a
<l spec=ecmascript>{{TypeError}}</l>.
1. Let |done| be [=?=] <a abstract-op>IteratorComplete</a>(|iterResult|).
1. If |done| is true:
1. Return [=end of iteration=].
1. Otherwise:
1. Let |V| be [=?=] <a abstract-op>IteratorValue</a>(|iterResult|).
1. Let |value| be the result of [=converted to an IDL value|converting=] |V| to an IDL
value of type |iterator|'s [=JS async iterator/type parameter=].
1. Return |value|.

</div>

<div algorithm>

To <dfn id="async-iterator-close" export lt="close an async iterator">close</dfn> an
<code><a lt="async iterator">async iterator&lt;<var ignore>T</var>&gt;</a></code> |iterator|,
with a reason |reason|:
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved

1. Let |iteratorRecord| be |iterator|'s [=JS async iterator/underlying record=].
1. Let |iteratorObj| be |iteratorRecord|.\[[Iterator]]
lucacasonato marked this conversation as resolved.
Show resolved Hide resolved
1. Let |returnMethod| be <a abstract-op>GetMethod</a>(|iteratorObj|, "<code>return</code>").
1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=]
|returnMethod|.\[[Value]].
1. If |returnMethod| is <emu-val>undefined</emu-val>, return [=a promise resolved with=]
{{undefined}}.
1. Let |returnResult| be <a abstract-op>Call</a>(|returnMethod|.\[[Value]], |iteratorObj|, « |reason| »).
1. If |returnResult| is an abrupt completion, return [=a promise rejected with=]
|returnResult|.\[[Value]].
1. Let |returnPromise| be [=a promise resolved with=] |returnResult|.\[[Value]].
1. Return the result of [=reacting=] to |returnPromise| with the following fulfillment steps,
given |returnPromiseResult|:
1. If <a abstract-op>Type</a>(|returnPromiseResult|) is not Object, [=JavaScript/throw=] a
<l spec=ecmascript>{{TypeError}}</l>.
1. Return {{undefined}}.

</div>

<div class="example" id="example-js-async-iterable">

<code>concatN</code> is an [=operation=] that returns a promise that will be fulfilled with the
concatenation of all the strings yielded by the async iterable passed to it. It stops
concatenating and closes the iterator once the async iterable has yielded <var ignore>maxN</var>
strings.

<pre>
interface I {
Promise&lt;DOMString> concatN(async iterable&lt;DOMString> strings, unsigned long maxN);
};
</pre>

<div algorithm="concatN">

The <code>concatN(|iterable|, |maxN|)</code> method steps are:

1. Let |promise| be [=a new promise=].
1. Let |result| be the empty string.
1. Let |n| be 0.
1. Let |iterator| be the result of <a lt="open an async iterable">opening</a> |iterable|.
1. Let |step| be a sequence of steps that will be used to process the async iterable:
1. Let |next| be the result of <a lt="get an async iterator next value">getting the next value</a> of |iterator|.
1. [=React=] to |next|:
- If |next| was fulfilled with value |v|:
1. If |v| is [=end of iteration=], [=resolve=] |promise| with |result|.
1. Set |result| to the result of concatenating |result| and |v|.
1. Set |n| to |n| + 1.
1. If |n| is |maxN|, then:
1. Let |finish| be the result of <a lt="close an async iterator">closing</a>
|iterator| with reason {{undefined}}.
1. [=React=] to |finish|:
- If |finish| was fulfilled, [=resolve=] |promise| with |result|.
- If |finish| was rejected with reason |r|, [=reject=] |promise| with |r|.
1. Otherwise:
1. Call |step|.
- If |next| was rejected with reason |r|, [=reject=] |promise| with |r|.
1. Call |step|.
1. Return |promise|.

</div>
</div>




<h4 id="js-record" oldids="es-record">Records — record&lt;|K|, |V|&gt;</h4>

IDL [=record=]&lt;|K|, |V|&gt; values are represented by
Expand Down Expand Up @@ -11226,6 +11440,23 @@ Note: The HTML Standard defines how a security check is performed. [[!HTML]]
1. Otherwise: if <a abstract-op>Type</a>(|V|) is Object and
there is an entry in |S| that has one of the
following types at position |i| of its type list,
* an [=async iterable type=]
* a [=nullable type|nullable=] version of any of the above types
* an [=annotated type=] whose [=annotated types/inner type=] is one of the above types
* a [=union type=], [=nullable type|nullable=] union type, or [=annotated type|annotated=] union type
that has one of the above types in its [=flattened member types=]

and after performing the following steps,

1. Let |method| be [=?=] <a abstract-op>GetMethod</a>(|V|, {{%Symbol.asyncIterator%}}).

|method| is not <emu-val>undefined</emu-val>, then remove from |S| all
other entries.

1. Otherwise: if <a abstract-op>Type</a>(|V|) is Object and
there is an entry in |S| that has one of the
following types at position |i| of its type list,
* an [=async iterable type=]
* a [=sequence type=]
* a [=nullable type|nullable=] [=sequence type=]
* an [=annotated type=] whose [=annotated types/inner type=] is a [=sequence type=]
Expand Down Expand Up @@ -11390,11 +11621,11 @@ Note: The HTML Standard defines how a security check is performed. [[!HTML]]
Generally, the inspection of the value at the distinguishing argument index does not have any
side effects, and the only side effects in the overload resolution algorithm are the result of
converting the JavaScript values to IDL values.
(An exception exists when one of the overloads has a [=sequence type=] or [=frozen array type=]
at the distinguishing argument index.
In this case, we attempt to get the {{%Symbol.iterator%}} property to determine the appropriate
overload, and perform the conversion of the distinguishing argument separately before continuing
with the next step.)
(An exception exists when one of the overloads has an [=async iterable type=], [=sequence type=]
or [=frozen array type=] at the distinguishing argument index.
In this case, we attempt to get the {{%Symbol.asyncIterator%}} / {{%Symbol.iterator%}} property
to determine the appropriate overload, and perform the conversion of the distinguishing argument
separately before continuing with the next step.)

At this point, we have determined which overload to use. We now
convert the remaining arguments, from the distinguishing argument onwards,
Expand Down
Loading