Skip to content
Merged
Changes from all commits
Commits
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
104 changes: 101 additions & 3 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ urlPrefix: https://tc39.es/ecma262/#; spec: ECMASCRIPT
text: normal completion; url: sec-completion-record-specification-type
text: NormalCompletion; url: sec-normalcompletion
text: throw completion; url: sec-completion-record-specification-type
text: Iterator Record; url: sec-iterator-records
url: sec-returnifabrupt-shorthands
text: ?
text: !
Expand Down Expand Up @@ -490,9 +491,106 @@ An <dfn>internal observer</dfn> is a [=struct=] with the following [=struct/item
1. <i id=from-observable-conversion><b>From Observable</b></i>: If |value|'s [=specific type=]
is an {{Observable}}, then return |value|.

1. Issue: Spec the <i><b>From async iterable</b></i> conversion steps which take place before
the iterable conversion steps. See <a
href=https://github.com/WICG/observable/issues/191>issue #191</a>.
1. <i id=from-async-iterable-conversion><b>From async iterable</b></i>: Let
|asyncIteratorMethod| be [=?=] [$GetMethod$](|value|, {{%Symbol.asyncIterator%}}).

Note: We use [$GetMethod$] instead of [$GetIterator$] because we're only probing for async
iterator protocol support, and we don't want to throw if it's not implemented.
[$GetIterator$] throws errors in BOTH of the following cases: (a) no iterator protocol is
implemented, (b) an iterator protocol is implemented, but isn't callable or its getter
throws. [$GetMethod$] lets us ONLY throw in the latter case.

1. If |asyncIteratorMethod|'s is undefined or null, then jump to the step labeled <a
href=#from-iterable-conversion>From iterable</a>.

1. Let |nextAlgorithm| be the following steps, given a {{Subscriber}} |subscriber| and an
[=Iterator Record=] |iteratorRecord|:

1. If |subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=] is
[=AbortSignal/aborted=], then return.

1. Let |nextPromise| be a {{Promise}}-or-undefined, initially undefined.

1. Let |nextCompletion| be [$IteratorNext$](|iteratorRecord|).

Note: We use [$IteratorNext$] here instead of [$IteratorStepValue$], because
[$IteratorStepValue$] expects the iterator's `next()` method to return an object that
can immediately be inspected for a value, whereas in the async iterator case, `next()`
is expected to return a Promise/thenable (which we wrap in a Promise and react to to get
that value).

1. If |nextCompletion| is a [=throw completion=], then:

1. [=Assert=]: |iteratorRecord|'s \[[Done]] is true.

1. Set |nextPromise| to [=a promise rejected with=] |nextRecord|'s \[[Value]].

1. Otherwise, if |nextRecord| is [=normal completion=], then set |nextPromise| to [=a
promise resolved with=] |nextRecord|'s \[[Value]].

Note: This is done in case |nextRecord|'s \[[Value]] is not *itself* already a
{{Promise}}.

1. [=React=] to |nextPromise|:

* If |nextPromise| was fulfilled with value |iteratorResult|, then:

1. If [$Type$](|iteratorResult|) is not Object, then run |subscriber|'s
{{Subscriber/error()}} method with a {{TypeError}} and abort these steps.

1. Let |done| be [$IteratorComplete$](|iteratorResult|).

1. If |done| is a [=throw completion=], then run |subscriber|'s
{{Subscriber/error()}} method with |done|'s \[[Value]] and abort these steps.

1. If |done|'s \[[Value]] is true, then run |subscriber|'s {{Subscriber/complete()}}
and abort these steps.

1. Run |nextAlgorithm|.

* If |nextPromise| was rejected with reason |r|, then run |subscriber|'s
{{Subscriber/error()}} method given |r|.

1. Return a [=new=] {{Observable}} whose [=Observable/subscribe callback=] is an algorithm that
takes a {{Subscriber}} |subscriber| and does the following:

1. If |subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=] is
[=AbortSignal/aborted=], then return.

1. Let |iteratorRecordCompletion| be [$GetIterator$](|value|, async).

Note: This both re-invokes any {{%Symbol.asyncIterator%}} method getters on |value|—note
that whether this is desirable is an extreme corner case, but it matches test
expectations; see <a href=https://github.com/WICG/observable/issues/127>issue#127</a> for
discussion—and invokes the protocol itself to obtain an [=Iterator Record=].

1. If |iteratorRecordCompletion| is a [=throw completion=], then run |subscriber|'s
{{Subscriber/error()}} method with |iteratorRecordCompletion|'s \[[Value]] and abort these
steps.

Note: This means we invoke the {{Subscriber/error()}} method synchronously with respect to
subscription, which is the only time this can happen for async iterables that are
converted to {{Observable}}s. In all other cases, errors are propagated to the observer
asynchronously, with microtask timing, by virtue of being wrapped in a rejected
{{Promise}} that |nextAlgorithm| [=reacts=] to. This synchronous-error-propagation
behavior is consistent with language constructs, i.e., **for-await of** loops that invoke
{{%Symbol.asyncIterator%}} and synchronously re-throw exceptions to catch blocks outside
the loop, before any [$Await|Awaiting$] takes place.

1. Let |iteratorRecord| be [=!=] |iteratorRecordCompletion|.

1. [=Assert=]: |iteratorRecord| is an [=Iterator Record=].

1. If |subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=] is
[=AbortSignal/aborted=], then return.

1. [=AbortSignal/add|Add the following abort algorithm=] to |subscriber|'s
[=Subscriber/subscription controller=]'s [=AbortController/signal=]:

1. Run [$AsyncIteratorClose$](|iteratorRecord|, [=NormalCompletion=](|subscriber|'s
[=Subscriber/subscription controller=]'s [=AbortSignal/abort reason=])).

1. Run |nextAlgorithm| given |subscriber| and |iteratorRecord|.

1. <i id=from-iterable-conversion><b>From iterable</b></i>: Let |iteratorMethod| be [=?=]
[$GetMethod$](|value|, {{%Symbol.iterator%}}).
Expand Down