From 14f1bdb8d585a48162ccccee8e9352790168c51a Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 25 Mar 2024 13:06:21 +0100 Subject: [PATCH] Add `async iterable` type to WebIDL This commit lifts the async iterator processing logic from the `ReadableStreamFromIterable` operation in the Streams spec, to a new WebIDL type `async iterable`. This will clean up the streams spec, and enable a change in the Fetch spec to directly allow taking async iterable as request/response bodies, without having to pass them to `ReadableStream.from` first. --- index.bs | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index d7ea1fa8..e8ef8bb4 100644 --- a/index.bs +++ b/index.bs @@ -68,6 +68,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 @@ -3350,6 +3351,7 @@ the following algorithm returns true.
interface-like
callback function
dictionary-like
+
async iterable
sequence-like
@@ -3365,6 +3367,7 @@ the following algorithm returns true. ● ● + ● boolean @@ -3378,6 +3381,7 @@ the following algorithm returns true. ● ● ● + ● numeric types @@ -3391,6 +3395,7 @@ the following algorithm returns true. ● ● ● + ● bigint @@ -3404,6 +3409,7 @@ the following algorithm returns true. ● ● ● + ● string types @@ -3417,6 +3423,7 @@ the following algorithm returns true. ● ● ● + ● object @@ -3430,6 +3437,7 @@ the following algorithm returns true. + symbol @@ -3443,6 +3451,7 @@ the following algorithm returns true. ● ● ● + ● interface-like @@ -3456,6 +3465,7 @@ the following algorithm returns true. ● ● ● + ● callback function @@ -3469,6 +3479,7 @@ the following algorithm returns true. (c) ● + ● dictionary-like @@ -3482,6 +3493,21 @@ the following algorithm returns true. ● + ● + + async iterable + + + + + + + + + + + + sequence-like @@ -3494,6 +3520,7 @@ the following algorithm returns true. + @@ -4059,7 +4086,7 @@ The following extended attributes are applicable to [=iterable declarations=]: -

Asynchronously iterable declarations

+

Asynchronously iterable declarations

An [=interface=] can be declared to be asynchronously iterable by using an asynchronously iterable declaration @@ -5665,6 +5692,7 @@ are known as object types. StringType Null identifier Null "sequence" "<" TypeWithExtendedAttributes ">" Null + "async iterable" "<" TypeWithExtendedAttributes ">" Null "object" Null "symbol" Null BufferRelatedType Null @@ -6166,6 +6194,26 @@ sequence is used. Any [=list=] can be implicitly treated as a sequence<|T|>, as long as it contains only [=list/items=] that are of type |T|. + + +

Async iterable types — async iterable<|T|>

+ +The async iterable<|T|> type is a parameterized +type whose values are references to objects that asynchronously yield zero or more values of type +|T|. + +Async iterables, unlike sequences, do not have a fixed length and can be infinite. Values are +asynchronously produced as the async iterable is iterated over. + +Async iterable 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. + +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.

Record types — record<|K|, |V|>

@@ -8064,6 +8112,122 @@ JavaScript Array values. +

Async iterable — async iterable<|T|>

+ +IDL async iterable<|T|> values are represented by JavaScript +[=Iterator=] records. + +
+ A JavaScript value |V| is [=converted to an IDL value|converted=] + to an IDL async iterable<T> value as follows: + + 1. If Type(|V|) is not Object, + [=JavaScript/throw=] a {{TypeError}}. + 1. Let |iteratorRecord| be [=?=] GetIterator(|V|, async). + 1. Return |iteratorRecord|. +
+ +An IDL async iterable<|T|> value can not be +[=converted to a JavaScript value=]. + +Note: Instead of returning an async iterable from an IDL operation, the operation should return an +[=interface=] that has an [=asynchronously iterable declaration=]. + +
Iterating async iterables
+ +
+ + To get the next value of an + async iterable<T> |iteratorRecord|, + perform the following steps: + + 1. Let |nextResult| be the result of calling IteratorNext(|iteratorRecord|). + 1. If |nextResult| is an abrupt completion, return [=a promise rejected with=] + |nextResult|.\[[Value]]. + 1. Let |nextPromise| be [=a promise resolved with=] |nextResult|.\[[Value]]. + 1. Return the result of [=reacting=] to |nextPromise| with with the following fulfillment + steps, given |iterResult|: + 1. If Type(|iterResult|) is not Object, [=JavaScript/throw=] a + {{TypeError}}. + 1. Let |done| be [=?=] IteratorComplete(|iterResult|). + 1. If |done| is true: + 1. Return [=end of iteration=]. + 1. Otherwise: + 1. Let |V| be [=?=] IteratorValue(|iterResult|). + 1. Let |value| be the result of [=converted to an IDL value|converting=] |V| to an IDL + value of type |T|. + 1. Return |value|. + +
+ +
+ + To finish iterating an + async iterable<T> |iteratorRecord|, + perform the following steps: + + 1. Let |iterator| be |iteratorRecord|.\[[Iterator]]. + 1. Let |returnMethod| be GetMethod(|iterator|, "return"). + 1. If |returnMethod| is an abrupt completion, return [=a promise rejected with=] + |returnMethod|.\[[Value]]. + 1. If |returnMethod| is undefined, return [=a promise resolved with=] + undefined. + 1. Let |returnResult| be Call(|returnMethod|.\[[Value]], |iterator|). + 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 Type(|returnPromiseResult|) is not Object, [=JavaScript/throw=] a + {{TypeError}}. + 1. Return undefined. + +
+ +
+ + concatN 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 finishes the iterator once the async iterable has yielded N strings. + +
+        interface I {
+          Promise<DOMString> concat(async iterable<DOMString> strings, unsigned long maxN);
+        };
+    
+ +
+ + The concatN(|iterable|, |maxN|) method steps are: + + 1. Let |promise| be [=a new promise=]. + 1. Let |result| be the empty string. + 1. Let |n| be 0. + 1. Let |step| be a sequence of steps that will be used to process the async iterable: + 1. Let |next| be the result of getting the next value of |iterable|. + 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 finishing iterating |iterable|. + 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|. + +
+
+ + + + +

Records — record<|K|, |V|>

IDL [=record=]<|K|, |V|> values are represented by @@ -11174,6 +11338,23 @@ Note: The HTML Standard defines how a security check is performed. [[!HTML]] 1. Otherwise: if Type(|V|) is Object and there is an entry in |S| that has one of the following types at position |i| of its type list, + * a [=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 [=?=] GetMethod(|V|, {{@@asyncIterator}}). + + |method| is not undefined, then remove from |S| all + other entries. + + 1. Otherwise: if Type(|V|) is Object and + there is an entry in |S| that has one of the + following types at position |i| of its type list, + * a [=async iterable type=] * a [=sequence type=] * a [=frozen array type=] * a [=nullable type|nullable=] version of any of the above types @@ -11342,11 +11523,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 {{@@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 a [=async iterable type=], [=sequence type=] + or [=frozen array type=] at the distinguishing argument index. + In this case, we attempt to get the {{@@asyncIterator}} / {{@@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,