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);It’s possible that there are UI systems where this would work but not the ones you listed above.
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... > 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. lineFileHandle = await open(lineFileHandle, "r");
should be lineFileHandle = await open(line, "r");
Same thing for the using version. let totalSize = 0;
const fileListHandle = await open("file-list.txt", "r");
try {
for await (const line of fileListHandle.readLines()) {
const lineFileHandle = await open(lineFileHandle, "r");
try {
totalSize += await lineFileHandle.read().bytesRead;
} finally {
await lineFileHandle.close();
}
}
} finally {
await fileListHandle.close();
}
console.log(totalSize);In the context of the parent question then it's the consistent standard syntax that makes things easier to read - something that indeed is impossible to see with an isolated example that, if anything, may look less clear than the syntax you're used to.
The reality is, there’s no better option at the moment.
Virtually every other ecosystem has concluded globals are not the best practice. (At least until we return to dependency injection containers where they are suddenly cool again but I digress.)
Singletons are bad in complicated, long-running processes, because you're in trouble if you want to have more than one of something, and cleanup can be a problem. A one-to-one relationship with the running process is problematic.
But JavaScript often runs in a disposable runtime environment that forces cleanup when terminated. For example, a web page or a web worker. Memory leaks usually aren't a problem and you can just treat it like arena allocation.
If you want more than one web page, it's very easy to do.
Similarly, if you're writing scripts using disposable Unix processes then a memory leak in a command isn't all that big a deal; you can sometimes get away with never freeing anything because the OS will do it.
As with everything, there is a time and a place, but if you understand the tradeoffs to recognize that time and place you won't be soliciting random advice from the internet, and thus won't hear the 'good'.
I get the impression that the JavaScript world largely doesn't care much for testing, though.
It’s pretty simple, but it has solved the testing problem for me because within that getResource function I can check if the environment being ran in is a test environment. If so, return a mocked instance. If not, return the real instance.
It’s pretty rudimentary, but it’s solved our issues.
For the browser context, I don’t have a good use case for ‘using’ (except for browser devs themselves where some code could be in JS now but impossible before).
Then again, people always surprise you with new use cases!
It appears all this does is avoids needing to manually call close (or equivalent)? While that is a nice addition, helping to avoid the situation where you forget, why does globals become the alternative? Isn't simply calling close manually the best option at the moment?
[1] https://github.com/tc39/proposal-explicit-resource-managemen...
That doesn’t appear to be the case, a resource can be returned, but this is block scoped. It appears to be closer to a using statement in C#.
But as to your question "Could anyone here help me understand a more robust example where `try...finally` just won't work", using is just basically syntactic sugar for try/finally, but I'm very much in favor of syntax that gets rid of lots of verbose boilerplate that can obscure the real purpose of your code.
One way is to use object pooling, but doing so in JS can be brittle because you have to remember to manually call `Pool.release(obj)`, `obj.free()` or whatever method you’ve chosen to return an object to the pool.
If a developer forgets to do this, you could exhaust the object pool, or if it’s growable, cause a memory leak! In a game’s update loop that could happen very quickly.
With this new feature, you could grab a short-lived object from the pool and automatically return it to the pool at the end of the method or loop.
Example - imagine this is inside an update method called 60 times per second:
for (const enemy of enemies) {
using pos = Pool.getVec3();
// do stuff with pos
enemy.setPosition(pos);
} // pos is returned to pool automatically
You asked about try/catch/finally. The downsides for this use-case are:* Big performance hit when you use it in a hot loop like this - the disposal could be happening ~10,000 times per second.
* Harder to remember to fill all your loops with try…finally, ugly to have double braces anytime you’re using a pooled object.
* It’s an abuse of syntax if you’re not actually catching any error.
The "best" way to do this in JS today is to have one function that opens and cleans up the input file (with a try/finally). That function calls another that opens and cleans up the db connection in the same way, and so on. That's verbose and makes your code nonlinear.
The new keyword brings Go's (and other languages') equivalent of 'defer' to JS. You don't need to worry about cleaning up, the 'using' keyword implies that it happens for you.
Now you can just call “await using foo(…)”, rather than the try/finally pattern, and get the automatic dispose call on leaving scope.
Kotlin has “use” with a similar effect, and i much prefer it over try/finally. Might be worth checking it out too.
Don't get me wrong there are alot of great things in ES6, but it was not quite the same language after...
Most programming languages are just different syntax on top of the same core primitives.
This is a bit of weird assignment, given that the Class syntactic sugar over Prototype was one of the marquee features of that release.
Ascribing that kind of motive to something as innocuous as syntactic sugar is silly at best. Most syntactic sugar I've seen is pretty clearly an attempt to make users' lives easier (whether successful or not is irrelevant).
Source: am an actual language designer.
Now it's "JavaScript used to be a little elegant". Fantastic.
More seriously:
1. This is typescript. Feel free to use js without it. (EDIT: This is wrong :)
2. This is a relatively easy feature, and there are equivalent features in some other mainstream languages.
You don't have to like it, I'm not sure I do, but this feels so dramatic.
Sir.
Wat. [0]
I could not possibly imagine a sentence I would be so vehemently disagreeing with.