@@ -1105,7 +1105,87 @@ For now, see [https://github.com/wicg/observable#operators](https://github.com/w
11051105<div algorithm>
11061106 The <dfn for=Observable method><code>finally(|callback|)</code></dfn> method steps are:
11071107
1108- 1. <span class=XXX> TODO: Spec this and use |callback|.</span>
1108+ 1. Let |sourceObservable| be [=this=] .
1109+
1110+ 1. Let |observable| be a [=new=] {{Observable}} whose [=Observable/subscribe callback=] is an
1111+ algorithm that takes a {{Subscriber}} |subscriber| and does the following:
1112+
1113+ 1. Let |finally callback steps| be the following steps:
1114+
1115+ 1. [=Invoke=] |callback|.
1116+
1117+ If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a> , then run
1118+ |subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps.
1119+
1120+ 1. [=AbortSignal/add|Add the algorithm=] |finally callback steps| to |subscriber|'s
1121+ [=Subscriber/signal=] .
1122+
1123+ Note: This is necessary to ensure |callback| gets invoked on *consumer-initiated*
1124+ unsubscription. In that case, |subscriber|'s [=Subscriber/signal=] gets
1125+ [=AbortSignal/signal abort|aborted=] , and neither the |sourceObserver|'s
1126+ [=internal observer/error steps=] nor [=internal observer/complete steps=] are invoked.
1127+
1128+ 1. Let |sourceObserver| be a new [=internal observer=] , initialized as follows:
1129+
1130+ : [=internal observer/next steps=]
1131+ :: Run |subscriber|'s {{Subscriber/next()}} method, given the passed in <var
1132+ ignore> value</var> .
1133+
1134+ : [=internal observer/error steps=]
1135+ :: 1. Run the |finally callback steps|.
1136+
1137+ <div class=example id=manual-finally-callback-steps>
1138+ <p> This "manual" invocation of |finally callback steps| is necessary to ensure
1139+ that |callback| is invoked on producer-initiated unsubscription. Without this,
1140+ we'd simply delegate to {{Subscriber/error()}} below, which first [=close a
1141+ subscription|closes=] the subscription, *and then* [=AbortSignal/signal
1142+ abort|aborts=] |subscriber|'s [=Subscriber/signal=] .</p>
1143+
1144+ <p> That means when |finally callback steps| eventually runs as a result of
1145+ abortion, |subscriber| would already be [=Subscriber/active|inactive=] . So if
1146+ |callback| throws an error during, it would never be plumbed through to
1147+ {{Subscriber/error()}} (that method is a no-op once
1148+ [=Subscriber/active|inactive=] ). See the following example which exercises this
1149+ case exactly:</p>
1150+
1151+ <pre highlight=js>
1152+ const controller = new AbortController();
1153+ const observable = new Observable(subscriber => {
1154+ subscriber.complete();
1155+ });
1156+
1157+ observable
1158+ .finally(() => {
1159+ throw new Error('finally error' );
1160+ })
1161+ .subscribe({
1162+ error: e => console.log('erorr passed through' ),
1163+ }, {signal: controller.signal});
1164+
1165+ controller.abort(); // Logs 'error passed through' .
1166+ </pre>
1167+ </div>
1168+
1169+ 1. Run |subscriber|'s {{Subscriber/error()}} method, given the passed in <var
1170+ ignore> error</var> .
1171+
1172+ Note: The |finally callback steps| possibly calls |subscriber|'s
1173+ {{Subscriber/error()}} method first, if |callback| throws an error. In that case, it
1174+ is still safe to call it again unconditionally, because the subscription will
1175+ already be closed, making the call a no-op.
1176+
1177+ : [=internal observer/complete steps=]
1178+ :: 1. Run the |finally callback steps|.
1179+
1180+ 1. Run |subscriber|'s {{Subscriber/complete()}} method.
1181+
1182+ 1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
1183+ |subscriber|'s [=Subscriber/signal=] .
1184+
1185+ 1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
1186+ given |sourceObserver| and |options|.
1187+
1188+ 1. Return |observable|.
11091189</div>
11101190
11111191
0 commit comments