@@ -1503,7 +1503,87 @@ For now, see
15031503<div algorithm>
15041504 The <dfn for=Observable method><code>finally(|callback|)</code></dfn> method steps are:
15051505
1506- 1. <span class=XXX> TODO: Spec this and use |callback|.</span>
1506+ 1. Let |sourceObservable| be [=this=] .
1507+
1508+ 1. Let |observable| be a [=new=] {{Observable}} whose [=Observable/subscribe callback=] is an
1509+ algorithm that takes a {{Subscriber}} |subscriber| and does the following:
1510+
1511+ 1. Let |finally callback steps| be the following steps:
1512+
1513+ 1. [=Invoke=] |callback|.
1514+
1515+ If <a spec=webidl lt="an exception was thrown">an exception |E| was thrown</a> , then run
1516+ |subscriber|'s {{Subscriber/error()}} method, given |E|, and abort these steps.
1517+
1518+ 1. [=AbortSignal/add|Add the algorithm=] |finally callback steps| to |subscriber|'s
1519+ [=Subscriber/signal=] .
1520+
1521+ Note: This is necessary to ensure |callback| gets invoked on *consumer-initiated*
1522+ unsubscription. In that case, |subscriber|'s [=Subscriber/signal=] gets
1523+ [=AbortSignal/signal abort|aborted=] , and neither the |sourceObserver|'s
1524+ [=internal observer/error steps=] nor [=internal observer/complete steps=] are invoked.
1525+
1526+ 1. Let |sourceObserver| be a new [=internal observer=] , initialized as follows:
1527+
1528+ : [=internal observer/next steps=]
1529+ :: Run |subscriber|'s {{Subscriber/next()}} method, given the passed in <var
1530+ ignore> value</var> .
1531+
1532+ : [=internal observer/error steps=]
1533+ :: 1. Run the |finally callback steps|.
1534+
1535+ <div class=example id=manual-finally-callback-steps>
1536+ <p> This "manual" invocation of |finally callback steps| is necessary to ensure
1537+ that |callback| is invoked on producer-initiated unsubscription. Without this,
1538+ we'd simply delegate to {{Subscriber/error()}} below, which first [=close a
1539+ subscription|closes=] the subscription, *and then* [=AbortSignal/signal
1540+ abort|aborts=] |subscriber|'s [=Subscriber/signal=] .</p>
1541+
1542+ <p> That means when |finally callback steps| eventually runs as a result of
1543+ abortion, |subscriber| would already be [=Subscriber/active|inactive=] . So if
1544+ |callback| throws an error during, it would never be plumbed through to
1545+ {{Subscriber/error()}} (that method is a no-op once
1546+ [=Subscriber/active|inactive=] ). See the following example which exercises this
1547+ case exactly:</p>
1548+
1549+ <pre highlight=js>
1550+ const controller = new AbortController();
1551+ const observable = new Observable(subscriber => {
1552+ subscriber.complete();
1553+ });
1554+
1555+ observable
1556+ .finally(() => {
1557+ throw new Error('finally error' );
1558+ })
1559+ .subscribe({
1560+ error: e => console.log('erorr passed through' ),
1561+ }, {signal: controller.signal});
1562+
1563+ controller.abort(); // Logs 'error passed through' .
1564+ </pre>
1565+ </div>
1566+
1567+ 1. Run |subscriber|'s {{Subscriber/error()}} method, given the passed in <var
1568+ ignore> error</var> .
1569+
1570+ Note: The |finally callback steps| possibly calls |subscriber|'s
1571+ {{Subscriber/error()}} method first, if |callback| throws an error. In that case, it
1572+ is still safe to call it again unconditionally, because the subscription will
1573+ already be closed, making the call a no-op.
1574+
1575+ : [=internal observer/complete steps=]
1576+ :: 1. Run the |finally callback steps|.
1577+
1578+ 1. Run |subscriber|'s {{Subscriber/complete()}} method.
1579+
1580+ 1. Let |options| be a new {{SubscribeOptions}} whose {{SubscribeOptions/signal}} is
1581+ |subscriber|'s [=Subscriber/signal=] .
1582+
1583+ 1. <a for=Observable lt="subscribe to an Observable">Subscribe</a> to |sourceObservable|
1584+ given |sourceObserver| and |options|.
1585+
1586+ 1. Return |observable|.
15071587</div>
15081588
15091589
0 commit comments