diff --git a/spec.bs b/spec.bs index 24c0651..58994eb 100644 --- a/spec.bs +++ b/spec.bs @@ -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. @@ -958,6 +959,128 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w |innerObserver| and |innerOptions|. +
+ The switchMap(|mapper|) 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 next steps (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 complete steps + 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: + + :
[=internal observer/next steps=]
+ :: 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 last pushed from |sourceObservable|. Then we immediately + subscribe to the new {{Observable}} that we're about to derive from |value|, + i.e., the most-recently pushed value from |sourceObservable|. + + 1. Set |activeInnerAbortController| to a [=new=] {{AbortController}}. + + 1. Run the [=switchmap process next value steps=] with |value|, |subscriber|, |mapper|, + and references 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| gets + aborted, due to |sourceObservable| pushing another newer value that will + replace the current "inner" subscription. + + : [=internal observer/error steps=] + :: Run |subscriber|'s {{Subscriber/error()}} method, given the passed in error. + + :
[=internal observer/complete steps=]
+ :: 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. Subscribe to |sourceObservable| + given |sourceObserver| and |options|. + + 1. Return |observable|. +
+ +
+ The switchmap process next value steps, given an {{any}} |value|, a {{Subscriber}} + |subscriber|, a {{Mapper}} |mapper|, and references 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 an exception |E| was thrown, 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 an exception |E| was thrown, 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 error. + + 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 switchMap complete steps 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. Subscribe to |innerObservable| given + |innerObserver| and |innerOptions|. +
+
The finally(|callback|) method steps are: