The verbosity comes because they are demonstrating both how to write the library code to support the feature, and how to consume it. But in reality a lot of the time someone else will have written the library code for you.
In this (still contrived) example, we end up having to do nested try/finally blocks.
Before:
let totalSize = 0;
let fileListHandle;
try {
fileListHandle = await open("file-list.txt", "r");
for await (const line of fileListHandle.readLines()) {
let lineFileHandle;
try {
lineFileHandle = await open(lineFileHandle, "r");
totalSize += await lineFileHandle.read().bytesRead;
} finally {
await lineFileHandle?.close();
}
}
} finally {
await fileListHandle?.close();
}
console.log(totalSize);
After:
let totalSize = 0;
try {
await using fileListHandle = getFileHandle("file-list.txt", "r");
for await (const line of fileListHandle.readLines()) {
await using lineFileHandle = getFileHandle(lineFileHandle, "r");
totalSize += await lineFileHandle.read().bytesRead;
}
}
console.log(totalSize);
Thank you for this example; it wasn't clear to me reading the article, but this is the main problem I was hope being solved. Will make writing tests much smoother.
As a consumer of the library, how will I know which calls need to have the using keyword? Is it a case of having to rely on the documentation stating so, or is there some other tell that something contains a Symbol.dispose function?
using typescript, you can imagine a little squiggle in vscode under an open without using "using", typescript can know that is object has a disposable symbol.
I wonder now if React / Vue / Svelte / SolidJs (and all the others) could use this to cleanup things as a finer grained way of handling unmounting of nodes, for example
Not likely. This is essentially syntactic sugar for `try ... finally`, the resource disposal is scope based. Node unmounting is linked to a different (longer) lifetime system than plain javascript scope.
It’s possible that there are UI systems where this would work but not the ones you listed above.
Yeah, the scope-based disposal jumped out at me from the examples. I suppose that means that anywhere you still have a reference to the resource alive, referenced by an object in the main node loop, that thing would never be automatically destroyed, right? You'd have to manually release it anyway. On the other hand, does this trigger if anything returned from that function goes out of scope? Or not until everything returned from it does?
I think you’re confusing scope and reachability. Maintaining a reference to an object has nothing to with whether or when it’s disposed in this TC39 language enhancement. Such a system _does_ exist in Object finalizers, but it’s hard to use correctly, especially in a language where it’s very easy to inadvertently retain references via closures. Resource disposal of this type needs to be much more predictable and can’t be left to the whims of the runtime. The docs on finalizers and WeakRefs are full of warnings not to expect them to be predictable or reliable.
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`
Maybe this is a dumb question, but for something to be reachable - i.e. not marked / swept by a garbage collector - doesn't it need a reference in the active scope? Weak references exist specifically to allow event handlers to be dereferenced at lazy intervals, but that's not what I'm talking about. What I mean is, if the above function returned a database connection to the Nodejs main loop, which stuffed it into a pool array of connections, wouldn't that still remain in scope for the remainder of the program unless it were explicitly deleted?
> if the above function returned a database connection to the Nodejs main loop, which stuffed it into a pool array of connections, wouldn't that still remain in scope for the remainder of the program unless it were explicitly deleted?
No, since these are block-scoped the original variable goes out of scope when the block it was declared in ends. The underlying _value_ that the variable is a reference to certainly can escape the block in a number of ways (assignments to existing variables or properties, closures), but this system doesn’t care about any of that, it’s directly equivalent to using try and finally, and finally blocks execute when you would expect them to.
In this (still contrived) example, we end up having to do nested try/finally blocks.
Before:
After: