@@ -1503,7 +1503,87 @@ For now, see
1503
1503
<div algorithm>
1504
1504
The <dfn for=Observable method><code>finally(|callback|)</code></dfn> method steps are:
1505
1505
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|.
1507
1587
</div>
1508
1588
1509
1589
0 commit comments