Though I agree that async context is better fit for this generally, the ERM should be good for telemetry around objects that have defined lifetime semantics, which is a step in the right direction you can use today
[0]: https://github.com/tc39/proposal-explicit-resource-managemen...
[1]: https://www.totaltypescript.com/typescript-5-2-new-keyword-u...
Edit: It's also a pending Stage 3 proposal for EcmaScript.
https://github.com/tc39/proposal-explicit-resource-managemen...
Short answer: Yes, Disposable can leak if you forget "using" it. And it will leak if the Disposable is not guarded by advanced GC mechanisms like the FinalizationRegistry.
Unlike C# where it's relatively easier to utilize its GC to dispose undisposed resources [2], properly utilizing FinalizationRegistry to do the same thing in JavaScript is not that simple. In response to our conversation, Ron is proposing adding the use of FinalizationRegistry as a best practice note [3], but only for native handles. It's mainly meant for JS engine developers.
Most JS developers wrapping anything inside a Disposable would not go through the complexity of integrating with FinalizationRegistry, thus cannot gain the same level of memory-safety, and will leak if not "using" it.
IMO this design will cause a lot of problems, misuses and abuses. But making JS to look more like C# is on Microsoft's agenda so they are probably not going to change anything.
[1]: https://github.com/tc39/proposal-explicit-resource-managemen...
[2]: https://stackoverflow.com/a/538238/1481095
[3]: https://github.com/tc39/proposal-explicit-resource-managemen...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
With this new using syntax, resources are disposed of when the object they are tied to goes out of _lexical scope_, which doesn’t need to worry about the runtime or object lifetimes at all. This example from the TC39 proposal makes it pretty clear:
function * g() {
using handle = acquireFileHandle(); // block-scoped critical resource
} // cleanup
{
using obj = g(); // block-scoped declaration
const r = obj.next();
} // calls finally blocks in `g`
https://github.com/tc39/proposal-explicit-resource-managemen...But assuming a javascript implementation where it works, I don't know what you mean by "language rule". The spec says what "using" means, and it doesn't require any behavior the language doesn't already have. The old spec said "When try using is parsed with an Expression, an implicit block-scoped binding is created for the result of the expression.", and the new one says "When a using declaration is parsed with BindingIdentifier Initializer, the bindings created in the declaration are tracked for disposal at the end of the containing Block or Module". Then the spec has an example implementation written in javascript, with the value being added to a (non-user-accessible) list. https://github.com/tc39/proposal-explicit-resource-managemen...
Is that the language rule you wanted?
It makes me realize I've never really considered the memory implications of doing something like:
const { someSmallPart } = getSomeMassiveObject();
I don't really know the lifetime of "massive object". Probably a bad on my part for not knowing if Javascript garbage collection is allowed to notice that the only thing with a reference is `someSmallPart` and could therefore release any other parts of the massive object. If the answer to that is "no" then there is no problem with the above pattern.If the answer is "yes", then things could get complicated. e.g.
async function getComplicatedObject() {
const db = await db.getConnection( ... );
const f = await file.open( ... );
return { db, f, [Symbol.asyncDispose]: () => {
db.close()
f.close()
}
}
{
await using { f } = getComplicatedObject();
// ... more stuff with awaits where the GC might decide `db` isn't used so it can clean
} // I would not expect a null ref error on `db` here when `asyncDispose` is called
I mean, you can handle the above even if the GC is allowed to be smart and discard `db` - it just makes it significantly more complicated and requires clever book-keeping.1. https://github.com/tc39/proposal-explicit-resource-managemen...
https://github.com/tc39/proposal-explicit-resource-managemen...
It’s a shame the author insisted on it looking like C# and sabotaged attempts to combine disposal with destructuring.
Also, there is no move operator without futzing around with DisposableStack. Though that could be introduced later; it’s not a day-zero design issue.
[1] https://github.com/tc39/proposal-explicit-resource-managemen...
{
await using { connection } = getConnection();
// Do stuff with connection
} // Automatically closed!
edit: according to the ES proposal issues, this might be something that doesn't actually exist, on purpose (it's rather ambiguous as to what will be disposed of): https://github.com/tc39/proposal-explicit-resource-managemen...I generally avoid new TS features (apart from typing that gets compiled away) until it looks like they are going to make their way into JavaScript, anyone know if thats being considered?
--
Edit: Yes it's being considered, looks likely, but not decided - https://github.com/tc39/proposal-explicit-resource-managemen...