Skip to content

Commit b39d1cb

Browse files
committed
Spec the finally() operator
1 parent df571e8 commit b39d1cb

File tree

1 file changed

+81
-1
lines changed

1 file changed

+81
-1
lines changed

Diff for: spec.bs

+81-1
Original file line numberDiff line numberDiff line change
@@ -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 =&gt; {
1552+
subscriber.complete();
1553+
});
1554+
1555+
observable
1556+
.finally(() =&gt; {
1557+
throw new Error('finally error');
1558+
})
1559+
.subscribe({
1560+
error: e =&gt; 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

Comments
 (0)