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

Spec the switchMap operator #130

Merged
merged 4 commits into from
Apr 29, 2024
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
123 changes: 123 additions & 0 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ interface Observable {
Observable take(unsigned long long amount);
Observable drop(unsigned long long amount);
Observable flatMap(Mapper mapper);
Observable switchMap(Mapper mapper);
Observable finally(VoidFunction callback);

// Promise-returning operators.
Expand Down Expand Up @@ -958,6 +959,128 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
|innerObserver| and |innerOptions|.
</div>

<div algorithm>
The <dfn for=Observable method><code>switchMap(|mapper|)</code></dfn> method steps are:

1. Let |sourceObservable| be [=this=].

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

1. Let |idx| be an {{unsigned long long}}, initially 0.

1. Let |outerSubscriptionHasCompleted| be a [=boolean=], initially false.

1. Let |activeInnerAbortController| be an {{AbortController}}-or-null, initially null.

Note: This {{AbortController}} is assigned to a new {{AbortController}} only by this
algorithm's <a href=#switchmap-next-steps>next steps</a> (below), and only assigned to
null by the [=switchmap process next value steps=], when the "inner" {{Observable}} either
completes or errors. This variable is used as a marker for whether there is currently an
active "inner" subscription. The <a href=#switchmap-complete-steps>complete steps</a>
below care about this, because if |sourceObservable| completes while there is an active
"inner" subscription, we do not immediately complete |subscriber|. In that case,
|subscriber|'s completion becomes blocked on the "inner" subscription's completion.

1. Let |sourceObserver| be a new [=internal observer=], initialized as follows:

: <dt id=switchmap-next-steps>[=internal observer/next steps=]</dt>
:: 1. If |activeInnerAbortController| is not null, then [=AbortController/signal abort=]
|activeInnerAbortController|.

Note: This "unsubscribes" from the "inner" {{Observable}} that was derived from the
value that was <i>last</i> pushed from |sourceObservable|. Then we immediately
subscribe to the <i>new</i> {{Observable}} that we're about to derive from |value|,
i.e., the <i>most-recently</i> pushed value from |sourceObservable|.

1. Set |activeInnerAbortController| to a [=new=] {{AbortController}}.

1. Run the [=switchmap process next value steps=] with |value|, |subscriber|, |mapper|,
and <b>references</b> to all of the following: |activeInnerAbortController|,
|outerSubscriptionHasCompleted|, and |idx|.

Note: The [=switchmap process next value steps=] will subscribe to the
{{Observable}} derived from |value| (if one such can be derived) and keep processing
values from it until either (1) its subscription becomes inactive (either by error or
completion), or (2) |activeInnerAbortController| <a href=#switchmap-next-steps>gets
aborted</a>, due to |sourceObservable| pushing another <i>newer</i> value that will
replace the current "inner" subscription.

: [=internal observer/error steps=]
:: Run |subscriber|'s {{Subscriber/error()}} method, given the passed in <var
ignore>error</var>.

: <dt id=switchmap-complete-steps>[=internal observer/complete steps=]</dt>
:: 1. Set |outerSubscriptionHasCompleted| to true.

Note: If |activeInnerAbortController| is not null, then we don't immediately
complete |subscriber|. Instead, the [=switchmap process next value steps=] will
complete |subscriber| when the inner subscription finally completes itself.

1. If |activeInnerAbortController| is null, run |subscriber|'s
{{Subscriber/complete()}} method.

1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
|subscriber|'s [=Subscriber/signal=].

1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
given |sourceObserver| and |options|.

1. Return |observable|.
</div>

<div algorithm>
The <dfn>switchmap process next value steps</dfn>, given an {{any}} |value|, a {{Subscriber}}
|subscriber|, a {{Mapper}} |mapper|, and <b>references</b> to all of the following: an
{{AbortController}} |activeInnerAbortController|, a [=boolean=]
|outerSubscriptionHasCompleted|, and an {{unsigned long long}} |idx| are to run these steps:

1. Let |mappedResult| be the result of [=invoking=] |mapper| with |value| and |idx|.

If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a>, then run
|subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps.

1. Set |idx| to |idx| + 1.

1. Let |innerObservable| be the result of calling {{Observable/from()}} with |mappedResult|.

If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a>, then run
|subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps.

1. Let |innerObserver| be a new [=internal observer=], initialized as follows:

: [=internal observer/next steps=]
:: Run |subscriber|'s {{Subscriber/next()}} method, given the passed in |value|.

: [=internal observer/error steps=]
:: Run |subscriber|'s {{Subscriber/error()}} method, given the passed in <var
ignore>error</var>.

Note: We don't have to set |activeInnerAbortController| to null here, to signal to the
{{Observable/switchMap()}} method steps above that the inner "subscription" has been
canceled. That's because calling |subscriber|'s {{Subscriber/error()}} method already
unsubscribes from the "outer" source Observable, so it will not be able to push any more
values to the {{Observable/switchMap()}} internal observer.

: [=internal observer/complete steps=]
:: 1. If |outerSubscriptionHasCompleted| is true, run |subscriber|'s
{{Subscriber/complete()}} method.

1. Otherwise, set |activeInnerAbortController| to null.

Note: Because this variable is a reference, it signals to the <a
href=#switchmap-complete-steps>switchMap complete steps</a> that there is no active
inner subscription.

1. Let |innerOptions| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is the
result of [=creating a dependent abort signal=] from the list
«|activeInnerAbortController|'s [=AbortController/signal=], |subscriber|'s
[=Subscriber/signal=]», using {{AbortSignal}}, and the [=current realm=].

1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |innerObservable| given
|innerObserver| and |innerOptions|.
</div>

<div algorithm>
The <dfn for=Observable method><code>finally(|callback|)</code></dfn> method steps are:

Expand Down
Loading