Replies: 1 comment 1 reply
-
Regarding this example:
Calling subscripton.add(() => {
try {
transaction.finalize();
}
finally {
connection.close();
}
}); which ensures that the connection is still closed. One of the benefits of const stack = new DisposableStack();
const connection = createDatabaseConn();
stack.defer(() => connection.close());
const transaction = connection.createTransaction();
stack.defer(() => transaction.finalize());
// later...
// this not only ensures that `transaction` is finalized before `connection` is closed, but that
// `connection` will be closed *even if* `transaction.finalize()` throws.
stack.dispose(); |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Problem Space
The current TC39 disposal proposal includes a
DisposableStack
that unfortunately probably doesn't suit our needs for ergonomics, but does include the idea that registered disposals (finalizations) should be fired in a last-in-first-out (LIFO) sort of way. The argument for doing this is that it's reasonable to assume that a resource that you set up that creates a secondary resource would need to be torn down after the secondary resource. For example, if you create a database connection, then a transaction, you wouldn't finalize the transaction before you closed the connection.Subscription
in RxJS, for a very long time have sort of a "mix" of behaviors when it comes to LIFO/FIFO execution of teardowns/finalizers.Subscription chains — those that are created by chains of operators — are generally executed LIFO due to composition. The consumer subscribes, which creates a subscription, that you then add a subscriptoin to some source to, which then gets a subscription added to it, and so on. When you subscribe to the Observable that is the result of your operator chain, it subscribes to the observable that is the result of the operator for it, which links the subscriptions, where the "downstream" subscription is the "parent" and the "upstream" subscription(s) is/are the child(ren). It repeats this all the way up to the head of the chain, the original source.
When you call unsubscribe on that subscription, it executes whatever logic it already had set up, then it unsubscribes it's children (mostly due to the circumstantial order in which they were added). Those children execute their logic and then unsubscribe their children, and so on. The result is, that at least between operators in RxJS, the execution of teardowns is done in a LIFO manner.
However, when it comes to teardowns have been
add
-ed toSubscription
, those are all executed in the same order in which they were added, making themFIFO
.Basically:
Now most people would work around this like so:
...but then what if there's an error thrown during
connection.createTransaction()
? If that's the case, there's a few things you can do:or
Proposed Solution: Execute Subscription Teardowns LIFO
That is to say this would work as follows:
Meaning that the following would just work as expected:
Counterpoints
The only real counterpoints I have here are:
Other thoughts
Symbol.dispose
onSubscription
(per the stage 3 TC39 proposal)add
-ing disposables that have theSymbol.dispose
method.use
that supports theSymbol.dispose
method, or augmentadd
to return a Disposable instance — Ironically, this is sort of what RxJS 5-6 was doing). Whereadd(() => void)
would return a disposable that would call and remove the function when disposed,add(Disposable)
would return the Disposable instance itself (alaDisposableStack
)Thank you to @rbuckton and @bakkot for the ideas here.
Beta Was this translation helpful? Give feedback.
All reactions