From e10d920b532ac95c01060027dfbd0472162fd324 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Mon, 29 Apr 2019 12:52:51 +0200 Subject: [PATCH] Add async_iterable support Fixes #580. --- index.bs | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index 95acc55f..20236459 100644 --- a/index.bs +++ b/index.bs @@ -83,6 +83,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262 text: Promise; url: sec-promise-objects text: Set; url: sec-set-objects text: SharedArrayBuffer; url: sec-sharedarraybuffer-objects + text: %AsyncIteratorPrototype%; url: sec-asynciteratorprototype text: %ErrorPrototype%; url: sec-properties-of-the-error-prototype-object text: %FunctionPrototype%; url: sec-properties-of-the-function-prototype-object text: %IteratorPrototype%; url: sec-%iteratorprototype%-object @@ -106,6 +107,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262 text: %PromiseProto_then%; url: sec-promise.prototype.then type: const; for: ECMAScript url: sec-well-known-symbols + text: @@asyncIterator text: @@iterator text: @@toStringTag text: @@unscopables @@ -134,6 +136,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262 text: GetFunctionRealm; url: sec-getfunctionrealm text: GetIterator; url: sec-getiterator text: GetMethod; url: sec-getmethod + text: IfAbruptRejectPromise; url: sec-ifabruptrejectpromise text: IsAccessorDescriptor; url: sec-isaccessordescriptor text: IsCallable; url: sec-iscallable text: IsConstructor; url: sec-isconstructor @@ -144,6 +147,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMA-262 text: IteratorStep; url: sec-iteratorstep text: IteratorValue; url: sec-iteratorvalue text: NewModuleEnvironment; url: sec-newmoduleenvironment + text: NewPromiseCapability; url: sec-newpromisecapability text: NormalCompletion; url: sec-normalcompletion text: ObjectCreate; url: sec-objectcreate text: OrdinaryDefineOwnProperty; url: sec-ordinarydefineownproperty @@ -1070,6 +1074,7 @@ The qualified name of an [=interface=] |interface| is defined as foll Stringifier StaticMember Iterable + AsyncIterable ReadOnlyMember ReadWriteAttribute ReadWriteMaplike @@ -1196,8 +1201,9 @@ describe the behaviors that can be implemented by an object, as if they were specified on the [=interface=] that [=includes=] them. [=Static attributes=], [=static operations=], [=special operations=] except for [=stringifiers=], and -[=iterable declaration|iterable=], [=maplike declaration|maplike=], and [=setlike declarations=] -cannot appear in [=interface mixin=] declarations. +[=iterable declaration|iterable=], [=asynchronously iterable declaration|asynchronously iterable=], +[=maplike declaration|maplike=], and [=setlike declarations=] cannot appear in [=interface mixin=] +declarations. As with interfaces, the IDL for [=interface mixins=] can be split into multiple parts by using partial interface mixin definitions @@ -1554,6 +1560,13 @@ and summarized in the following informative table: + + [=Asynchronously iterable declarations=] + ● + + + + [=Maplike declarations=] ● @@ -4126,6 +4139,138 @@ The following extended attributes are applicable to [=iterable declarations=]: +

Asynchronously iterable declarations

+ +An [=interface=] can be declared to be asynchronously iterable by using an +asynchronously iterable declaration +(matching AsyncIterable) in the body of the +[=interface=]. + +
+    interface interface_identifier {
+      async_iterable<key_type, value_type>;
+    };
+
+ +Objects that [=implement=] an [=interface=] that is declared to be asynchronously iterable support +being iterated over asynchronously to obtain a sequence of values. + +Note: In the ECMAScript language binding, an interface that is asynchronously iterable will have +entries, keys, values, +and {{@@asyncIterator}} properties on its [=interface prototype object=]. + +Prose accompanying an [=interface=] with an [=asynchronously iterable declaration=] must define a +get the next iteration result algorithm. +This algorithm receives a [=this=] value, which is an instance of the [=interface=] that it +is defined for, and the current state. +It must return a {{Promise}} that either resolves with undefined – to signal the end of the +iteration – or a tuple with three elements: + +1. a value of the first type given in the declaration; +1. a value of the second type given in the declaration; +1. an opaque value that is passed back to the next invocation of the algorithm, + +[=Interfaces=] with an [=asynchronously iterable declaration=] must not have any +[=interface members=] named "entries", "keys", or "values", +or have any [=inherited interfaces=] that have [=interface members=] with these names. + +
+ + Consider the following interface SessionManager, which allows access + to a number of Session objects keyed by username: + +
+        [Exposed=Window]
+        interface SessionManager {
+          Session getSessionForUser(DOMString username);
+
+          async_iterable<DOMString, Session>;
+        };
+
+        [Exposed=Window]
+        interface Session {
+          readonly attribute DOMString username;
+          // ...
+        };
+    
+ + The behavior of the iterator could be defined like so: + +
+ + To [=get the next iteration result=] for SessionManager, run the + following steps: + + 1. Let |promise| be a new promise. + 1. Let |key| be as follows: +
+ : If current state is not given + :: the username in this's open sessions that is first in lexicographical + order + + : If current state is given + :: the username in this's open sessions that is next in lexicographical + order, after current state +
+ 1. If there is no such |key|, then: + 1. Resolve |promise| with undefined. + 1. Otherwise: + 1. Let |session| be the Session object corresponding to |key|. + 1. Resolve |promise| with (|username|, |session|, |username|). + 1. Return |promise|. + +
+ + In the ECMAScript language binding, the [=interface prototype object=] for the + SessionManager [=interface=] has a values method that is a + function, which, when invoked, returns an asynchronous iterator object that itself has a + next method that returns the next value to be iterated over. + It has keys and entries methods that iterate over the usernames of + session objects and (username, Session) object pairs, respectively. + It also has a {{@@asyncIterator}} method that allows a SessionManager + to be used in a for await..of loop that has the same value as the + entries method: + +
+        // Get an instance of SessionManager.
+        // Assume that it has sessions for two users, "anna" and "brian".
+        var sm = getSessionManager();
+
+        typeof SessionManager.prototype.values;            // Evaluates to "function"
+        var it = sm.values();                              // values() returns an iterator object
+        typeof it.next;                                    // Evaluates to "function"
+
+        // This loop will log "anna" and then "brian".
+        for await (let username of sm.keys()) {
+          console.log(username);
+        }
+
+        // Yet another way of accomplishing the same.
+        for await (let [username, session] of sm) {
+          console.log(username);
+        }
+    
+
+ +An [=interface=] must not have more than one [=asynchronously iterable declaration=]. +The [=inherited interfaces=] of an [=interface=] with an [=asynchronously iterable declaration=] +must not also have an [=asynchronously iterable declaration=]. +An [=interface=] with an [=asynchronously iterable declaration=] and its [=inherited interfaces=] +must not have a [=maplike declaration=], [=setlike declaration=], or [=iterable declaration=]. + +The following extended attributes are applicable to [=asynchronously iterable declarations=]: +[{{Exposed}}], +[{{SecureContext}}]. + +Issue: these [=extended attributes=] are not currently taken into account. +When they are, the effect will be as you would expect. + +
+    AsyncIterable :
+        "async_iterable" "<" TypeWithExtendedAttributes "," TypeWithExtendedAttributes ">" ";"
+
+ +

Maplike declarations

An [=interface=] can be declared to be @@ -4196,6 +4341,7 @@ also have a [=maplike declaration=]. A maplike interface and its [=inherited interfaces=] must not have an [=iterable declaration=], +an [=asynchronously iterable declaration=], a [=setlike declaration=], or an [=indexed property getter=]. @@ -4289,6 +4435,7 @@ also have a [=setlike declaration=]. A setlike interface and its [=inherited interfaces=] must not have an [=iterable declaration=], +an [=asynchronously iterable declaration=], a [=maplike declaration=], or an [=indexed property getter=]. @@ -8755,6 +8902,7 @@ with the same [=identifier=]. There also must not be more than one [=stringifier=] or more than one [=iterable declaration=], +[=asynchronously iterable declaration=], [=maplike declaration=] or [=setlike declaration=] across those interfaces. @@ -11501,6 +11649,45 @@ and the String "values" if the interface has a [=setlike declaration=]. +
@@asyncIterator
+ +If the [=interface=] has an [=asynchronously iterable declaration=], then a property must exist +whose name is the {{@@asyncIterator}} symbol, with attributes +{ \[[Writable]]: true, \[[Enumerable]]: false, \[[Configurable]]: true } +and whose value is a [=function object=]. + +The location of the property is determined as follows: + +* If the interface was declared with the [{{Global}}] [=extended attribute=], + then the property exists on the single object that [=implements=] the interface. +* Otherwise, the property exists solely on the interface’s [=interface prototype object=]. + +
+ + The [=function object=] is a [=built-in function object=] that, when invoked, must behave as + follows: + + 1. Let |object| be the result of calling [$ToObject$] on the this value. + 1. If |object| [=is a platform object=], then [=perform a security check=], passing: + * the platform object |object|, + * the identifier "@@asyncIterator", and + * the type "method". + 1. Let |interface| be the [=interface=] on which the [=asynchronously iterable declaration=] + is declared. + 1. If |object| does not [=implement=] |interface|, then [=ECMAScript/throw=] a + {{ECMAScript/TypeError}}. + 1. Let |iterator| be a newly created [=default asynchronous iterator object=] for |interface| + with |object| as its target and iterator kind "key+value". + 1. Return |iterator|. +
+ +The value of the {{@@asyncIterator}} [=function object=]’s length property +is the Number value 0. + +The value of the {{@@asyncIterator}} [=function object=]’s name property +is the String value "entries". + +
forEach
If the [=interface=] has any of the following: @@ -11607,7 +11794,7 @@ property is the String value "forEach".
entries
-If the [=interface=] has an [=iterable declaration=], +If the [=interface=] has an [=iterable declaration=] or an [=asynchronously iterable declaration=], then an entries data property must exist with attributes { \[[Writable]]: true, \[[Enumerable]]: true, \[[Configurable]]: true } and whose value is a [=function object=]. @@ -11625,10 +11812,12 @@ If the interface has a [=pair iterator=], then the [=function object=] is the value of the {{@@iterator}} property. +If the interface has an [=asynchronously iterable declaration=], then the [=function object=] is +the value of the {{@@asyncIterator}} property.
keys
-If the [=interface=] has an [=iterable declaration=], +If the [=interface=] has an [=iterable declaration=] or an [=asynchronously iterable declaration=], then a keys data property must exist with attributes { \[[Writable]]: true, \[[Enumerable]]: true, \[[Configurable]]: true } and whose value is a [=function object=]. @@ -11654,7 +11843,7 @@ then the [=function object=] is {{%ArrayProto_keys%}}. * the identifier "keys", and * the type "method". 1. Let |interface| be the [=interface=] - on which the [=iterable declaration=] is declared on. + on which the [=iterable declaration=] is declared. 1. If |object| does not [=implement=] |interface|, then [=ECMAScript/throw=] a {{ECMAScript/TypeError}}. 1. Let |iterator| be a newly created [=default iterator object=] @@ -11662,6 +11851,25 @@ then the [=function object=] is {{%ArrayProto_keys%}}. 1. Return |iterator|. +
+ + If the interface has an [=asynchronously iterable declaration=], then the method, when invoked, + must behave as follows: + + 1. Let |object| be the result of calling [$ToObject$] on the this value. + 1. If |object| [=is a platform object=], then [=perform a security check=], passing: + * the platform object |object|, + * the identifier "keys", and + * the type "method". + 1. Let |interface| be the [=interface=] on which the [=asynchronously iterable declaration=] + is declared. + 1. If |object| does not [=implement=] |interface|, then [=ECMAScript/throw=] a + {{ECMAScript/TypeError}}. + 1. Let |iterator| be a newly created [=default asynchronous iterator object=] for |interface| + with |object| as its target and iterator kind "key". + 1. Return |iterator|. +
+ The value of the [=function object=]’s length property is the Number value 0. The value of the [=function object=]’s name property is the String value "keys". @@ -11669,8 +11877,7 @@ The value of the [=function object=]’s name property
values
-If the [=interface=] has an -[=iterable declaration=], +If the [=interface=] has an [=iterable declaration=] or an [=asynchronously iterable declaration=], then a values data property must exist with attributes { \[[Writable]]: true, \[[Enumerable]]: true, \[[Configurable]]: true } and whose value is a [=function object=]. @@ -11697,7 +11904,7 @@ the value of the {{@@iterator}} property. * the identifier "entries", and * the type "method". 1. Let |interface| be the [=interface=] - on which the [=iterable declaration=] is declared on. + on which the [=iterable declaration=] is declared. 1. If |object| does not [=implement=] |interface|, then [=ECMAScript/throw=] a {{ECMAScript/TypeError}}. 1. Let |iterator| be a newly created [=default iterator object=] @@ -11705,6 +11912,25 @@ the value of the {{@@iterator}} property. 1. Return |iterator|. +
+ + If the interface has an [=asynchronously iterable declaration=], then the method, when invoked, + must behave as follows: + + 1. Let |object| be the result of calling [$ToObject$] on the this value. + 1. If |object| [=is a platform object=], then [=perform a security check=], passing: + * the platform object |object|, + * the identifier "entries", and + * the type "method". + 1. Let |interface| be the [=interface=] on which the [=asynchronously iterable declaration=] + is declared. + 1. If |object| does not [=implement=] |interface|, then [=ECMAScript/throw=] a + {{ECMAScript/TypeError}}. + 1. Let |iterator| be a newly created [=default asynchronous iterator object=] for |interface| + with |object| as its target and iterator kind "value". + 1. Return |iterator|. +
+ The value of the [=function object=]’s length property is the Number value 0. The value of the [=function object=]’s name property is the String value "values". @@ -11811,6 +12037,123 @@ is the result of concatenating the [=identifier=] of the [=interface=] and the string " Iterator". +
Default asynchronous iterator objects
+ +A default asynchronous iterator object +for a given [=interface=], target and iteration kind is an object whose \[[Prototype]] +[=internal slot=] is the [=asynchronous iterator prototype object=] for the [=interface=]. + +A [=default asynchronous iterator object=] has internal values: + +* its target, which is an object whose values are to be iterated, +* its kind, which is the iteration kind, +* its ongoing promise, which is a {{Promise}}, +* its state, which is an opaque value used to store the position of the iterator by the + algorithm to [=get the next iteration result=], or null. + +When a [=default asynchronous iterator object=] is first created, its state is not given. + +Note: [=Default asynchronous iterator objects=] do not have [=class strings=]; when +Object.prototype.toString() is called on a +[=default asynchronous iterator object=] of a given [=interface=], the [=class string=] of the +[=asynchronous iterator prototype object=] of that [=interface=] is used. + + +
Asynchronous iterator prototype object
+ +The +asynchronous iterator prototype object +for a given [=interface=] is an object that exists for every interface that has an +[=asynchronously iterable declaration=]. +It serves as the prototype for [=default asynchronous iterator objects=] for the interface. + +The \[[Prototype]] [=internal slot=] of an [=asynchronous iterator prototype object=] must be +{{%AsyncIteratorPrototype%}}. + +
+ + An [=asynchronous iterator prototype object=] must have a next data + property with attributes + { \[[Writable]]: true, \[[Enumerable]]: true, \[[Configurable]]: true } + and whose value is a [=built-in function object=] that behaves as follows: + + 1. Let |interface| be the [=interface=] for which the + [=asynchronous iterator prototype object=] exists. + + 1. Let |thisValidationPromiseCapability| be [=!=] [$NewPromiseCapability$]({{%Promise%}}). + + 1. Let |object| be the result of calling [$ToObject$] on the + this value. + + 1. [$IfAbruptRejectPromise$](|object|, |thisValidationPromiseCapability|). + + 1. If |object| [=is a platform object=], then [=perform a security check=], passing: + * the platform object |object|, + * the identifier "next", and + * the type "method". + + If this threw an exception |e|, then: + 1. Perform [=!=] [$Call$](|thisValidationPromiseCapability|.\[[Reject]], + undefined, « |e| »). + 1. Return |thisValidationPromiseCapability|.\[[Promise]]. + + 1. If |object| is not a [=default asynchronous iterator object=] for |interface|, then: + 1. Issue: [=Realm=] check? + 1. Let |error| be a new {{ECMAScript/TypeError}}. + 1. Perform [=!=] [$Call$](|thisValidationPromiseCapability|.\[[Reject]], + undefined, « |error| »). + 1. Return |thisValidationPromiseCapability|.\[[Promise]]. + + 1. Let |nextSteps| be the following steps: + 1. Let |nextPromiseCapability| be [=!=] [$NewPromiseCapability$]({{%Promise%}}). + 1. Let |oldState| be |object|'s state. + 1. If |oldState| is null, then: + 1. Let |result| be [$CreateIterResultObject$](undefined, + true). + 1. Perform [=!=] [$Call$](|nextPromiseCapability|.\[[Resolve]], + undefined, « |result| »). + 1. Return |nextPromiseCapability|.\[[Promise]]. + 1. Let |kind| be |object|'s kind. + 1. Let |nextPromise| be the result of + [=get the next iteration result|getting the next iteration result=] with |object|'s + target as [=this=] and |oldState| as the [=current state=]. + 1. Let |resolveSteps| be the following steps, given |next|: + 1. Set |object|'s ongoing promise to undefined. + 1. If |next| is undefined, then: + 1. Set |object|'s state to null. + 1. Let |result| be [$CreateIterResultObject$](undefined, + true). + 1. Perform [=!=] [$Call$](|nextPromiseCapability|.\[[Resolve]], + undefined, « |result| »). + 1. Otherwise: + 1. Let (|key|, |value|, |newState|) be |next|. + 1. Set |object|'s state to |newState|. + 1. Let |result| be the [=iterator result=] for (|key|, |value|) and |kind|. + 1. Perform [=!=] [$Call$](|nextPromiseCapability|.\[[Resolve]], + undefined, « |result| »). + 1. Let |onFulfilled| be [=!=] [$CreateBuiltinFunction$](|resolveSteps|, « »). + 1. Perform [=!=] [$PerformPromiseThen$](|nextPromise|, |onFulfilled|, + undefined, |nextPromiseCapability|). + 1. Return |nextPromiseCapability|.\[[Promise]]. + + 1. If |object|'s ongoing promise is not undefined, then: + 1. Let |afterOngoingPromiseCapability| be [=!=] [$NewPromiseCapability$]({{%Promise%}}). + 1. Let |onFulfilled| be [=!=] [$CreateBuiltinFunction$](|nextSteps|, « »). + 1. Perform [=!=] [$PerformPromiseThen$](|object|'s ongoing promise, |onFulfilled|, + undefined, |afterOngoingPromiseCapability|). + 1. Set |object|'s ongoing promise to |afterOngoingPromiseCapability|.\[[Promise]]. + + 1. Otherwise: + 1. Run |nextSteps| and set |object|'s ongoing promise to the result. + + 1. Return |object|'s ongoing promise. +
+ +Issue: The [=class string=] of an [=asynchronous iterator prototype object=] for a given +[=interface=] is the result of concatenating the [=identifier=] of the [=interface=] and the string +" Iterator". + +

Maplike declarations

Any object that [=implements=] an [=interface=]