From 69ad0de85654466a03f312d4e86ddd68360245d0 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Thu, 22 Aug 2024 19:20:03 +0200 Subject: [PATCH] Spec `inspect()` operator (#168) --- spec.bs | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/spec.bs b/spec.bs index 0ada9c8..69c5ad9 100644 --- a/spec.bs +++ b/spec.bs @@ -326,7 +326,19 @@ dictionary SubscriptionObserver { VoidFunction complete; }; +callback ObservableInspectorAbortHandler = undefined (any value); + +dictionary ObservableInspector { + ObservableSubscriptionCallback next; + ObservableSubscriptionCallback error; + VoidFunction complete; + + VoidFunction subscribe; + ObservableInspectorAbortHandler abort; +}; + typedef (ObservableSubscriptionCallback or SubscriptionObserver) ObserverUnion; +typedef (ObservableSubscriptionCallback or ObservableInspector) ObservableInspectorUnion; dictionary SubscribeOptions { AbortSignal signal; @@ -362,6 +374,7 @@ interface Observable { Observable drop(unsigned long long amount); Observable flatMap(Mapper mapper); Observable switchMap(Mapper mapper); + Observable inspect(optional ObservableInspectorUnion inspect_observer = {}); Observable finally(VoidFunction callback); // Promise-returning operators. @@ -1085,6 +1098,115 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w |innerObserver| and |innerOptions|. +
+ The inspect(|inspector_union|) method steps are: + + 1. Let |subscribe callback| be a {{VoidFunction}}-or-null, initially null. + + 1. Let |next callback| be a {{ObservableSubscriptionCallback}}-or-null, initially null. + + 1. Let |error callback| be a {{ObservableSubscriptionCallback}}-or-null, initially null. + + 1. Let |complete callback| be a {{VoidFunction}}-or-null, initially null. + + 1. Let |abort callback| be a {{ObservableInspectorAbortHandler}}-or-null, initially null. + + 1. Process |inspector_union| as follows: +
+
If |inspector_union| is an {{ObservableSubscriptionCallback}}
+
+ 1. Set |next callback| to |inspector_union|. + +
If |inspector_union| is an {{ObservableInspector}}
+
+ 1. If {{ObservableInspector/subscribe}} [=map/exists=] in |inspector_union|, then set + |subscribe callback| to it. + + 1. If {{ObservableInspector/next}} [=map/exists=] in |inspector_union|, then set + |next callback| to it. + + 1. If {{ObservableInspector/error}} [=map/exists=] in |inspector_union|, then set + |error callback| to it. + + 1. If {{ObservableInspector/complete}} [=map/exists=] in |inspector_union|, then set + |complete callback| to it. + + 1. If {{ObservableInspector/abort}} [=map/exists=] in |inspector_union|, then set + |abort callback| to it. +
+
+ + 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. If |subscribe callback| is not null, then [=invoke=] it. + + If an exception |E| was thrown, then run + |subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps. + + Note: The result of this is that |sourceObservable| is never subscribed to. + + 1. If |abort callback| is not null, then [=AbortSignal/add|add the following abort + algorithm=] to |subscriber|'s [=Subscriber/subscription controller=]'s + [=AbortController/signal=]: + + 1. [=Invoke=] |abort callback| with |subscriber|'s [=Subscriber/subscription + controller=]'s [=AbortController/signal=]'s [=AbortSignal/abort reason=]. + + If an exception |E| was thrown, then + [=report the exception=] |E|. + + 1. Let |sourceObserver| be a new [=internal observer=], initialized as follows: + + : [=internal observer/next steps=] + :: 1. If |next callback| is not null, then [=invoke=] |next callback| with the passed in + |value|. + + If an exception |E| was thrown, then: + + 1. [=AbortSignal/Remove=] |abort callback| from |subscriber|'s + [=Subscriber/subscription controller=]'s [=AbortController/signal=]. + + Note: This step is important, because the |abort callback| is only meant to be + called for *consumer-initiated* unsubscriptions. When the producer terminates + the subscription (via |subscriber|'s {{Subscriber/error()}} or + {{Subscriber/complete()}} methods) like below, we have to ensure that + |abort callback| is not run. + + Issue: This matches Chromium's implementation, but consider holding a reference + to the originally-passed-in {{SubscribeOptions}}'s {{SubscribeOptions/signal}} + and just invoking |abort callback| when *it* aborts. The result is likely the + same, but needs investigation. + + 1. Run |subscriber|'s {{Subscriber/error()}} method, given |E|, and return. + + 1. Run |subscriber|'s {{Subscriber/next()}} method with the passed in |value|. + + : [=internal observer/error steps=] + :: [=AbortSignal/Remove=] |abort callback| from |subscriber|'s [=Subscriber/subscription + controller=]'s [=AbortController/signal=], and run |subscriber|'s + {{Subscriber/error()}} method, given the passed in error. + + : [=internal observer/complete steps=] + :: [=AbortSignal/Remove=] |abort callback| from |subscriber|'s [=Subscriber/subscription + controller=]'s [=AbortController/signal=], and run |subscriber|'s + {{Subscriber/complete()}} method. + + 1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is + |subscriber|'s [=Subscriber/subscription controller=]'s [=AbortController/signal=]. + + 1. Subscribe to |sourceObservable| + given |sourceObserver| and |options|. + + 1. Return |observable|. + + + /dom/observable/tentative/observable-inspect.any.js + +
+
The finally(|callback|) method steps are: