7 ms·
I feel like the toy examples in the article might only make sense to people who already know why they want this feature. The examples don't make it at all obvio
by killthebuddha 3y ago
I feel like the toy examples in the article might only make sense to people who already know why they want this feature. The examples don't make it at all obvious to me why I would want this feature. The new version in the examples have more code, more indirection, and more magic (in the sense that it relies directly on a specific property of the runtime). Could anyone here help me understand a more robust example where `try...finally` just won't work (or at least would be patently less readable/etc)?
- hn_throwaway_99 3y agoIt's essentially the exact same thing as the using statement in C#, so if you search for that you should be able to find more info. 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.
- robby_w_g 3y agoThe proposal[1] explains several cases where try…finally is verbose or even can result in subtle errors. The upshot for me is that this feature adds RAII to JavaScript. It makes resource management convenient and more maintainable. Seems like a no brainer to me. [1] https://github.com/tc39/proposal-explicit-resource-management#definitions https://github.com/tc39/proposal-explicit-resource-managemen...
- DougBTX 3y ago> The upshot for me is that this feature adds RAII to JavaScript 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#.
- TJSomething 3y agoBased on my reading of the spec, you can't actually return a resource, since the disposal semantics need to map exactly to calling [Symbol.dispose] in a finally block at the end of the current block. Also, unlike C#, you can have multiple using statements in the same block, which will be disposed of in LIFO order.
- monkpit 3y agoYou can create a resource without “using” it immediately, though.
- moron4hire 3y agoYou can have multiple using statements over the same block in C#.
- grose 3y agoI've only had one scenario where I actually needed something like this (and I don't know if the `using` proposal helps). I have a library (Prolog interpreter) that uses a Wasm module originally written in C, so I have to manage the memory manually. If users iterate through or close a query (e.g. with `for await`) it's fine and cleans itself up. But, it's possible to to create an AsyncGenerator, never call .return() on it (which won't run the `finally` block), and have that reference garbage collected. I used a finalizer on a local variable in the async generator to work around this :)
- jasonjmcghee 3y agoOne way you could think of it is, what if you’re using a library and it has a method supporting “using” functionality. 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.
- deleted 3y ago[deleted]
- kushan2020 3y agoBy abstracting away the countless ways a function can handle cleanups, you are providing a single, uniform interface that anyone can implement. This concept is akin to the usage of `.then`, where you agree upon an interface that allows people to execute asynchronous work. The syntax sugar of async/await relies on the existence of a `then` key, demonstrating similar concept.
- davnicwil 3y agothis is a great explanation, thanks! 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.
- computomatic 3y agoScoped resources are essential for avoiding global singletons. The JS/Node ecosystem acts like (and often believes) globally-scoped singletons are a recommended pattern for things like database connections. 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.)
- skybrian 3y agoIt can be useful take a step back and think about where best practices come from. What problems do they solve? 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.
- AlphaSite 3y agoI think from a threading perspective that’s a reasonable statement. But it’s also about creating manageable abstractions, very limited singleton usage ok, but it can easily get out of hand and lead to hard to reason about code.
- randomdata 3y agoSingletons are considered 'bad' under general advice because they can make testing hard. 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.
- ajanuary 3y agoThe 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);
- chrisabrams 3y agoThank 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.
- no_wizard 3y agoI 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
- semiquaver 3y agoNot 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.
- noduerme 3y ago
- gentleman11 3y agoJavaScript used to be a little elegant in its simplicity. JavaScript now is getting complicated. Syntactic sugar, I think, is only there to build a moat around a tech to justify a salary premium and reduce the number of newcomers. You end up with a dozen arcane ways to do the same thing, and you have to be fluent with all of them in order to read your teammates code. It makes the language harder to use, while adding no new functionality
- hungryforcodes 3y agoThis is really true. I feel somehow the problem came when the functional programming gang hijacked it and forced ES6 on us. Promises were so counter intuitive they had to create shortly after async / wait... Don't get me wrong there are alot of great things in ES6, but it was not quite the same language after...
- efdee 3y agoWhat would you prefer instead of promises? Callbacks?
- salwadordali 3y agoHow would you solve the async/await issue then? I think JS handles asynchronicity very elegantly. You just need to create a proper mental model around it. I don't want to go back to the old times of callback hell, that's for sure.
- manigandham 3y agoasync/await and promises are both just syntax on top of yield/generators. Most programming languages are just different syntax on top of the same core primitives.
- merrywhether 3y ago> functional programming gang This is a bit of weird assignment, given that the Class syntactic sugar over Prototype was one of the marquee features of that release.
- 3y ago
- bastawhiz 3y agoConsider the case where you want to iterate over an input file, query data from a database with those inputs, and write the outputs as lines to an output file. The function can fail anywhere: between opening the input file, connecting to the database, or opening the output file. With try/finally, you need to keep the variables outside the scope of your try block and check whether each one has been set in the finally (and clean them up). I.e., if the database connection fails, you don't want to try closing the output file because you haven't opened it yet. The resulting code is sloppy. 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.
- deleted 3y ago[deleted]
- ladon86 3y agoWhen writing a game in JS, it’s very important to minimize garbage collection - too much will cause periodic stuttering and freezes. To avoid GC you want to minimize runtime allocations and mostly allocate upfront and reuse that memory. 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.