diff --git a/spec.bs b/spec.bs index a82b81c..3260061 100644 --- a/spec.bs +++ b/spec.bs @@ -1105,7 +1105,87 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
finally(|callback|)
method steps are:
- 1. TODO: Spec this and use |callback|.
+ 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 |finally callback steps| be the following steps:
+
+ 1. [=Invoke=] |callback|.
+
+ If an exception |E| was thrown, then run
+ |subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps.
+
+ 1. [=AbortSignal/add|Add the algorithm=] |finally callback steps| to |subscriber|'s
+ [=Subscriber/signal=].
+
+ Note: This is necessary to ensure |callback| gets invoked on *consumer-initiated*
+ unsubscription. In that case, |subscriber|'s [=Subscriber/signal=] gets
+ [=AbortSignal/signal abort|aborted=], and neither the |sourceObserver|'s
+ [=internal observer/error steps=] nor [=internal observer/complete steps=] are invoked.
+
+ 1. Let |sourceObserver| 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=]
+ :: 1. Run the |finally callback steps|.
+
+ This "manual" invocation of |finally callback steps| is necessary to ensure + that |callback| is invoked on producer-initiated unsubscription. Without this, + we'd simply delegate to {{Subscriber/error()}} below, which first [=close a + subscription|closes=] the subscription, *and then* [=AbortSignal/signal + abort|aborts=] |subscriber|'s [=Subscriber/signal=].
+ +That means when |finally callback steps| eventually runs as a result of + abortion, |subscriber| would already be [=Subscriber/active|inactive=]. So if + |callback| throws an error during, it would never be plumbed through to + {{Subscriber/error()}} (that method is a no-op once + [=Subscriber/active|inactive=]). See the following example which exercises this + case exactly:
+ ++const controller = new AbortController(); +const observable = new Observable(subscriber => { + subscriber.complete(); +}); + +observable + .finally(() => { + throw new Error('finally error'); + }) + .subscribe({ + error: e => console.log('erorr passed through'), + }, {signal: controller.signal}); + +controller.abort(); // Logs 'error passed through'. ++