[HN Gopher] JavaScript's New Superpower: Explicit Resource Manag...
       ___________________________________________________________________
        
       JavaScript's New Superpower: Explicit Resource Management
        
       Author : olalonde
       Score  : 283 points
       Date   : 2025-05-17 05:23 UTC (17 hours ago)
        
 (HTM) web link (v8.dev)
 (TXT) w3m dump (v8.dev)
        
       | qprofyeh wrote:
       | Can someone explain why they didn't go with (anonymous) class
       | destructors? Or something other than a Symbol as special object
       | key. Especially when there are two Symbols (different one for
       | asynchronous) which makes it a leaky abstraction, no?
        
         | Garlef wrote:
         | Because this approach also works for stuff that is not a class
         | instance.
        
         | matharmin wrote:
         | Destructors I other languages are typically used for when the
         | object is garbage collected. That has a whole bunch of
         | associated issues, which is why the pattern is often avoided
         | these days.
         | 
         | The dispose methods on the other hand are called when the
         | variable goes out of scope, which is much more predictable. You
         | can rely on for example a file being closed ot a lock released
         | before your method returns.
         | 
         | JavaScript is already explicit about what is synchronous versus
         | asynchronous everywhere else, and this is no exception. Your
         | method needs to wait for disposing to complete, so if disposing
         | is asynchronous, your method must be asynchronous as well. It
         | does get a bit annoying though that you end up with a double
         | await, as in `await using a = await b()` if you're not used to
         | that syntax.
         | 
         | As for using symbols - that's the same as other functionality
         | added over time, such as iterator. It gives a nice way for the
         | support to be added in a backwards-compatible way. And it's
         | mostly only library authors dealing with the symbols - a
         | typical app developer never has to touch it directly.
        
         | senfiaj wrote:
         | For garbage collected languages destructors cannot be called
         | synchronously in most cases because the VM must make sure that
         | the object is inaccessible first. So it will not work very
         | deterministically, and also will expose the JS VM internals.
         | For that JS already has WeakRef and FinalizationRegistry.
         | 
         | https://waspdev.com/articles/2025-04-09/features-that-every-...
         | https://waspdev.com/articles/2025-04-09/features-that-every-...
         | 
         | But even Mozilla doesn't recommend to use them because they're
         | quite unpredictable and might work differently in different
         | engines.
        
         | feverzsj wrote:
         | Because javascript is uncivilized.
        
         | masklinn wrote:
         | Destructors require deterministic cleanup, which advanced GCs
         | can't do (and really don't want to either from an efficiency
         | perspective). Languages with advanced GCs have "finalizers"
         | called during collection which are thus extremely unreliable
         | (and full of subtle footguns), and are normally only used as a
         | last resort solution for native resources (FFI wrappers).
         | 
         | Hence many either had or ended up growing means of lexical
         | (scope-based) resource cleanup whether,
         | 
         | - HoF-based (smalltalk, haskell, ruby)
         | 
         | - dedicated scope / value hook (python[1], C#, Java)
         | 
         | - callback registration (go, swift)
         | 
         | [1]: Python originally used destructors thanks to a refcounting
         | GC, but the combination of alternate non-refcounted
         | implementations, refcount cycles, and resources like locks not
         | having guards (and not wanting to add those with no clear
         | utility) led to the introduction of context managers
        
           | nh2 wrote:
           | What does "HoF" stand for?
        
             | masklinn wrote:
             | higher order function, function taking an other function (/
             | block).
             | 
             | E.g. in Ruby you can lock/unlock a mutex, but the normal
             | way to do it would be to pass a block to
             | `Mutex#synchronize` which is essentially just
             | def synchronize         lock         begin           yield
             | ensure           unlock         end       end
             | 
             | and called as:                 lock.synchronize {         #
             | protected code here       }
        
         | pwdisswordfishz wrote:
         | There is no such thing as an anonymous property in JavaScript.
         | Your question doesn't make sense. What else could this possibly
         | be?
        
       | demarq wrote:
       | So... drop
        
         | mettamage wrote:
         | What do you mean by that? Is `drop` a language construct in
         | another language?
        
           | brigandish wrote:
           | It's also in this comment, which reads like Gen Zed slang.
           | 
           | https://news.ycombinator.com/item?id=44012969
        
           | dminik wrote:
           | Yes, it's called Drop in rust: https://doc.rust-
           | lang.org/std/ops/trait.Drop.html
        
         | brigandish wrote:
         | Drop?
        
           | DemocracyFTW2 wrote:
           | it's an annoying usage because you never know whether it
           | means "sth new appeared" or "sth old stopped being available"
        
             | pacifika wrote:
             | Drop and biweekly
        
             | john2x wrote:
             | New drop just dropped
        
         | lucasyvas wrote:
         | The more this stuff gets introduced the more I'm convinced to
         | use Rust everywhere. I'm not saying this is the Rust way - it's
         | actually reminiscent of Python/C#. But, Rust does it better.
         | 
         | If we keep going down these roads, Rust actually becomes the
         | simpler language as it was designed with all of these goals
         | instead of shoe-horning them back in.
        
           | CryZe wrote:
           | First it was "Why Do Animals Keep Evolving into Crabs?", now
           | it's "Why Do Programming Languages Keep Evolving into Crabs?"
        
           | demarq wrote:
           | Not sure I agree or maybe we do.
           | 
           | I think JavaScript should remain simple. If we really need
           | this functionality we can bring in defer. But as a 1:1 with
           | what is in golang. This in between of python and golang is
           | too much for what JavaScript is supposed to be.
           | 
           | I definitely think that the web needs a second language with
           | types, resource management and all sorts of structural guard
           | rails. But continuing to hack into JavaScript is not it.
        
       | CreepGin wrote:
       | Need to dig into this more, but I built OneJS [1] (kinda like
       | React Native but for Unity), and at first glance this looks
       | perfect for us(?). Seems to be super handy for Unity where you've
       | got meshes, RenderTextures, ComputeBuffers, and NativeContainers
       | allocations that all need proper disposal outside of JS. Forcing
       | disposal at lexical scopes, we can probs keep memory more stable
       | during long Editor sessions or when hot-reloading a lot.
       | 
       | [1] https://github.com/Singtaa/OneJS
        
       | smashah wrote:
       | I would like to petition JSLand to please let go of the word
       | "use" and all of its derivatives. Cool feature though, looking
       | forward to using (smh) it.
        
         | hahn-kev wrote:
         | They're just adopting the same syntax that C# has used for a
         | long time
        
           | smashah wrote:
           | Ok, but it doesn't make it any less meaningless.
           | 
           | async, await, let, var, const, try, catch, yield are all
           | meaningful and precise keywords
           | 
           | "use" "using" on the other hand is not a precise word at all.
           | To any non c# person it could be used to replace any of the
           | above words!
        
       | bvrmn wrote:
       | Context managers: exist.
       | 
       | JS: drop but we couldn't occupy a possibly taken name, Symbol for
       | the win!
       | 
       | It's hilariously awkward.
        
         | demarq wrote:
         | nah, Symbol has been traits for javascript for quite a while
         | eg. Symbol.iterator
         | 
         | It's the "dispose" part where the new name is decided.
        
           | rounce wrote:
           | Traits in the way that a roller-skate is a car.
        
         | masklinn wrote:
         | > JS: drop but we couldn't occupy a possibly taken name, Symbol
         | for the win!
         | 
         | You're about a decade late to the party?
         | 
         | That is the entire point of symbols and "well known symbols",
         | and why they were introduced back in ES6.
        
           | bvrmn wrote:
           | And I didn't use it because there was no need.
           | 
           | Resource scoping is important feature. Context managers (in
           | python) are literally bread and butter for everyday tasks.
           | 
           | It's awkward not because of Symbol, it introduces new syntax
           | tied to existing implicit scopes. It's kinda fragile based on
           | Go experience. Explicit scoping is a way more predictable.
        
       | havkom wrote:
       | Reminds me of C#.. IDisposible and IAsyncDisposible in C# helps a
       | lot to write good mechanisms for things that should actually be
       | abstracted in a nice way (such as locks handling, queue
       | mechanisms, temporary scopes for impersonation, etc).
        
         | masklinn wrote:
         | It's basically lifted from C#'s, the original proposal makes no
         | secret of it and cites all of Python's context managers, Java's
         | try with resources, C#'s using statements, and C#'s using
         | declarations. And `using` being the keyword and `dispose` the
         | hook method is a pretty big hint.
        
         | pwdisswordfishz wrote:
         | That's because the author of the proposal is from Microsoft and
         | has repeatedly shot down counter-suggestions that made the
         | syntax look different from C#.
         | 
         | https://github.com/tc39/proposal-explicit-resource-managemen...
         | 
         | https://github.com/tc39/proposal-explicit-resource-managemen...
         | 
         | https://github.com/tc39/proposal-explicit-resource-managemen...
         | 
         | https://github.com/tc39/proposal-explicit-resource-managemen...
        
           | spankalee wrote:
           | That looks like a lot of very reasonable responses to me.
        
       | russellbeattie wrote:
       | Maybe it's just me, but [Symbol.dispose]() seems like a really
       | hacky way to add that functionality to an Object. Here's their
       | example:                   using readerResource = {
       | reader: response.body.getReader(),             [Symbol.dispose]()
       | {                 this.reader.releaseLock();             },
       | };
       | 
       | First, I had to refresh my memory on the new object definition
       | shorthand: In short, you can use a variable or expression to
       | define a key name by using brackets, like: let key = "foo"; {
       | [key]: "bar"}, and secondly you don't have to write { "baz" :
       | function(p) { ... } }, you can instead write { baz(p) {...} }.
       | OK, got it.
       | 
       | So, if I'm looking at the above example correctly, they're
       | implementing what is essentially an Interface-based definition of
       | a new "resource" object. ( _If it walks like a duck, and
       | quacks..._ )
       | 
       | To make a "resource", you'll tack on a new magical method to your
       | POJO, identified not with a standard name (like
       | Object.constructor() or Object.__proto__), but with a name that
       | is a result of whatever "Symbol.dispose" evaluates to. Thus the
       | above definition of { [Symbol.dispose]() {...} }, which
       | apparently the "using" keyword will call when the object goes out
       | of scope.
       | 
       | Do I understand that all correctly?
       | 
       | I'd think the proper JavaScript way to do this would be to either
       | make a new object specific modifier keyword like the way getters
       | and setters work, or to create a new global object named
       | "Resource" which has the needed method prototypes that can be
       | overwritten.
       | 
       | Using Symbol is just weird. Disposing a resource has nothing to
       | do with Symbol's core purpose of creating unique identifiers.
       | Plus it looks fugly and is definitely confusing.
       | 
       | Is there another example of an arbitrary method name being called
       | by a keyword? It's not a function parameter like async/await uses
       | to return a Promise, it's just a random method tacked on to an
       | Object using a Symbol to define the name of it. Weird!
       | 
       | Maybe I'm missing something.
        
         | paavohtl wrote:
         | JS has used "well-known symbols"[1] to allow extending /
         | overriding the functionality of objects for about 10 years. For
         | example, an object is an iterable if it has a
         | `[Symbol.iterator]` property. Symbols are valid object keys;
         | they are not just string aliases.
         | 
         | [1] https://developer.mozilla.org/en-
         | US/docs/Web/JavaScript/Refe...
        
         | demarq wrote:
         | Yes you are missing something. You are not supposed to call
         | these methods, they are for the runtime.
         | 
         | more specifically, javascript will call the [Symbol.dispose]
         | when it detects you are exiting the scope of a "using"
         | declaration.
        
         | uasi wrote:
         | > create a new global object named "Resource" which has the
         | needed method prototypes that can be overwritten.
         | 
         | those methods could conflict with existing methods already used
         | in other ways if you'd want to make an existing class a
         | subclass of Resource.
        
         | Spivak wrote:
         | I hadn't considered how blessed I was to have __enter__ /
         | __exit__ in Python for context managers and the more general
         | Protocol concept that can be used for anything because lordy
         | that definition looks ugly as sin. Even Perl looks on in horror
         | of the sins JS has committed.
        
         | masklinn wrote:
         | > To make a "resource", you'll tack on a new magical method to
         | your POJO, identified not with a standard name [...] nothing to
         | do with Symbol's core purpose of creating unique identifiers.
         | 
         | The core purpose and original reason why Symbol was introduced
         | in JS is the ability to create non-conflicting but well known /
         | standard names, because the language had originally reserved no
         | namespace for such and thus there was no way to know _any_ name
         | would be available (and not already monkey patched onto
         | existing types, including native types).
         | 
         | > Is there another example of an arbitrary method name being
         | called by a keyword? It's not a function parameter like
         | async/await uses to return a Promise, it's just a random method
         | tacked on to an Object using a Symbol to define the name of it.
         | Weird!
         | 
         | `Symbol.iterator` called by `for...of` is literally the
         | original use case for symbols.
         | 
         | > I'd think the proper JavaScript way to do this would be to
         | either make a new object specific modifier keyword like the way
         | getters and setters work, or to create a new global object
         | named "Resource" which has the needed method prototypes that
         | can be overwritten.
         | 
         | Genuinely: what are you talking about.
        
         | rafram wrote:
         | > identified not with a standard name (like
         | Object.constructor() or Object.__proto__)
         | 
         | __proto__ was a terrible mistake. Google "prototype pollution";
         | there are too many examples to link. In a duck-typed language
         | where the main mechanism for data deserialization is
         | JSON.parse(), you can't trust the value of any plain string
         | key.
        
         | jitl wrote:
         | Symbols are a very safe way to introduce new "protocols" in
         | either the language standard or for application code. This is
         | because Symbol can never conflict with existing class
         | definitions. If we use a string name for the method, then
         | existing code semantics change.
         | 
         | Here are the well-known symbols that my NodeJS 22 offers when I
         | `Symbol.<tab>`:                   Symbol.asyncDispose
         | Symbol.asyncIterator         Symbol.dispose
         | Symbol.hasInstance         Symbol.isConcatSpreadable
         | Symbol.iterator         Symbol.keyFor         Symbol.length
         | Symbol.match         Symbol.matchAll         Symbol.replace
         | Symbol.search         Symbol.species         Symbol.split
         | Symbol.toPrimitive         Symbol.toStringTag
         | Symbol.unscopables
        
       | sufianrhazi wrote:
       | This is a great upcoming feature, I wrote some practical advice
       | (a realistic example, how to use it with
       | TypeScript/vite/eslint/neovim/etc...) about it a few months ago
       | here: https://abstract.properties/explicit-resource-management-
       | is-...
        
       | creata wrote:
       | This seems error-prone, for at least two reasons:
       | 
       | * If you accidentally use `let` or `const` instead of `using`,
       | everything will work but silently leak resources.
       | 
       | * Objects that contain resources need to manually define
       | `dispose` and call it on their children. Forgetting to do so will
       | lead to resource leaks.
       | 
       | It looks like defer dressed up to resemble RAII.
        
         | akdor1154 wrote:
         | There is pretty strong precedent for this design over in .NET
         | land - if it was awful or notably inferior to `defer` I'm sure
         | the Chrome engineering team would have taken notice.
        
           | creata wrote:
           | C# has the advantage of being a typed language, which allows
           | compilers and IDEs to warn in the circumstances I mentioned.
           | JavaScript isn't a typed language, which limits the potential
           | for such warnings.
           | 
           | Anyway, I didn't say it was "inferior to defer", I said that
           | it seemed more error-prone than RAII in languages like Rust
           | and C++.
           | 
           | Edit: Sorry if I'm horribly wrong (I don't use C#) but the
           | relevant code analysis rules look like CA2000 and CA2213.
        
             | masklinn wrote:
             | > Anyway, I didn't say it was "inferior to defer", I said
             | that it seemed more error-prone than RAII in languages like
             | Rust and C++.
             | 
             | It is, but RAII really isn't an option if you have an
             | advanced GC, as it is lifetime-based and requires
             | deterministic destruction of individual objects, and much
             | of the performance of an advanced GC comes from not doing
             | that.
             | 
             | Most GC'd language have some sort of finalizers (so does
             | javascript: https://developer.mozilla.org/en-
             | US/docs/Web/JavaScript/Refe...) but those are unreliable
             | and often have subtle footguns when used for cleanup.
        
             | legulere wrote:
             | It's still difficult to get right in cases where you hold a
             | disposable as a member. Its not obvious if disposables
             | passed in also get disposed and what's right depends on the
             | situation (think a string based TextWriter getting passed
             | in a byte-based Stream) and you will need to handle double
             | disposes.
             | 
             | Further C# has destructors that get used as a last resort
             | effort on native resources like file descriptors.
        
               | creata wrote:
               | > Further C# has destructors that get used as a last
               | resort effort on native resources like file descriptors.
               | 
               | True, I was going to mention that, but I saw that JS also
               | has "finalization registries", which seem to provide
               | finalizer support in JS, so I figured it wasn't a
               | _fundamental_ difference.
        
         | akoboldfrying wrote:
         | Exactly this. No idea why you were downvoted.
         | 
         | The problem they are trying to solve is that the programmer
         | could forget to wrap an object creation with try. But their
         | solution is just kicking the can down the road, because now the
         | programmer could forget to write "using"!
         | 
         | I was thinking that a much better solution would be to simply
         | add a no-op default implementation of dispose(), and call it
         | whenever _any_ object hits end-of-scope with refcount=1, and
         | drop the  "using" keyword entirely, since that way programmers
         | couldn't forget to write "using". But then I remembered that
         | JavaScript doesn't have refcounts, and we can't assume that
         | function calls to which the object has been passed have not
         | kept references to it, expecting it to still exist in its
         | undisposed state later.
         | 
         | OTOH, if there really is no "nice" solution to detecting this
         | kind of "escape", it means that, under the new system, writing
         | "using" must be dangerous -- it can lead to dispose() being
         | called when some function call stored a reference to the object
         | somewhere, expecting it to still exist in its undisposed state
         | later.
        
           | mistercow wrote:
           | Another point there is that JS has always gone to great
           | lengths not to expose the GC in any way. For example, you
           | can't enumerate a WeakSet, because that would cause behavior
           | to be GC dependent. Calling dispose when an object is
           | collected would very explicitly cause the GC to have semantic
           | effects, and I think that goes strongly against the JS
           | philosophy.
        
             | masklinn wrote:
             | FinalizationRegistry was added, like, 5 years ago.
        
               | rafram wrote:
               | Finalizers aren't destructors. The finalizer doesn't get
               | access to the object being GC'd, for one. But even more
               | crucially, the spec allows the engine to call your
               | finalizer anywhere between long after the object has been
               | GC'd, and never.
               | 
               | They're basically a nice convenience for noncritical
               | resource cleanup. You can't rely on them.
        
               | masklinn wrote:
               | Yes? Congratulation you know what a finalizer is?
               | 
               | I was replying to this:
               | 
               | > would very explicitly cause the GC to have semantic
               | effects, and I think that goes strongly against the JS
               | philosophy.
               | 
               | Do you disagree that a finalizer provides for exactly
               | that and thus can not be "strongly against the JS
               | philosophy"?
        
               | mistercow wrote:
               | I mean it's an explicit violation of that philosophy as
               | noted in the proposal:
               | 
               | > For this reason, the W3C TAG Design Principles
               | recommend against creating APIs that expose garbage
               | collection. It's best if WeakRef objects and
               | FinalizationRegistry objects are used as a way to avoid
               | excess memory usage, or as a backstop against certain
               | bugs, rather than as a normal way to clean up external
               | resources or observe what's allocated.
        
               | pwdisswordfishz wrote:
               | Yes, it and WeakRef are exceptions, but they are the only
               | ones, designed to be deniable - if you delete
               | globalThis.WeakRef; and delete
               | globalThis.FinalizationRegistry; you go back to not
               | exposing GC at all. WeakRef even has a special exception
               | in the spec in that the .constructor property is
               | optional, specifically so that handing a weak reference
               | to some code does not necessarily enable it to create
               | more weak references, so you can be also limited as to
               | _which_ objects ' GC you can observe.
               | 
               | Though another problem is that the spec does not clearly
               | specify when an object may be collected or allow the
               | programmer to control GC in any way, which means relying
               | on FinalizationRegistry may lead to leaks/failure to
               | finalize unused resources (bad, but sometimes tolerable)
               | or worse, use-after-free bugs (outright fatal) - see e.g.
               | https://github.com/tc39/ecma262/issues/2650
        
               | mistercow wrote:
               | Fair, I wasn't aware of that. But even so, there's a big
               | difference between a wonky feature intended for niche
               | cases and documented almost entirely in terms of caveats,
               | and "this is the new way to dispose of resources".
               | 
               | And the point that this kind of thing is against the JS
               | philosophy is pretty explicit:
               | 
               | https://w3ctag.github.io/design-principles/#js-gc
        
           | morsecodist wrote:
           | I feel it doesn't make sense to conflate resource management
           | with garbage collection. The cleanup actions here are more
           | like releasing a lock, deleting temporary files, or closing a
           | connection. This doesn't lead to a lack of safety. These
           | resources already need to deal with these uninitialised
           | states. For example, consider a lock management object. You
           | shouldn't assume you have the lock just because you have a
           | reference to the manager resource. It's totally normal to
           | have objects that require some sort of initialization.
        
         | demurgos wrote:
         | What you describe is already the status quo today. This
         | proposal is still a big improvement as it makes resource
         | management less error prone when you're aware to use it and
         | _standardizes the mechanism through the symbol_. This enables
         | tooling to lint for the situations you're describing based on
         | type information.
        
         | 0xfffafaCrash wrote:
         | Here's some relevant discussion about some of the footguns:
         | 
         | https://github.com/typescript-eslint/typescript-eslint/issue...
         | 
         | https://github.com/tc39/proposal-explicit-resource-managemen...
         | 
         | I imagine there will eventually be lint rules for this
         | somewhere and many of those using such a modern feature are
         | likely to be using static analysis via eslint to help mitigate
         | the risks here, but until it's more established and understood
         | and lint rules are fleshed out and widely adopted, there is
         | risk here for sure.
         | 
         | https://github.com/typescript-eslint/typescript-eslint/issue...
         | 
         | To me it seems a bit like popular lint libraries just going
         | ahead and adding the rule would make a big difference here
        
       | roschdal wrote:
       | JavaScript new features: segmentation faults, memory leaks,
       | memory corruption and core dumps.
        
         | cluckindan wrote:
         | Nah, it still doesn't let you allocate or free memory manually.
        
       | TekMol wrote:
       | Their first example is about having to have a try/finally block
       | in a function like this:                   function
       | processData(response) {             const reader =
       | response.body.getReader();             try {
       | reader.read()             } finally {
       | reader.releaseLock();             }         }
       | 
       | So that the read lock is lifted even if reader.read() throws an
       | error.
       | 
       | Does this only hold for long running processes? In a browser
       | environment or in a cli script that terminates when an error is
       | thrown, would the lock be lifted when the process exits?
        
         | teraflop wrote:
         | The spec just says that when a block "completes" its execution,
         | however that happens (normal completion, an exception, a
         | break/continue statement, etc.) the disposal must run. This is
         | the same for "using" as it is for "try/finally".
         | 
         | When a process is forcibly terminated, the behavior is
         | inherently outside the scope of the ECMAScript specification,
         | because at that point the interpreter cannot take any further
         | actions.
         | 
         | So what happens depends on what kind of object you're talking
         | about. The example in the article is talking about a "stream"
         | from the web platform streams spec. A stream, in this sense, is
         | a JS object that only exists within a JS interpreter. If the JS
         | interpreter goes away, then it's meaningless to ask whether the
         | lock is locked or unlocked, because the lock no longer exists.
         | 
         | If you were talking about some kind of OS-allocated resource
         | (e.g. allocated memory or file descriptors), then there is
         | generally some kind of OS-provided cleanup when a process
         | terminates, no matter how the termination happens, even if the
         | process itself takes no action. But of course the details are
         | platform-specific.
        
         | jitl wrote:
         | Browser web pages are quintessential long running programs! At
         | least for Notion, a browser tab typically lives much longer
         | (days to weeks) than our server processes (hours until next
         | deploy). They're an event loop like a server often with
         | multiple subprocesses, very much _not_ a run-to-completion CLI
         | tool. And errors do not terminate a web page.
         | 
         | The order of execution for unhandled errors is well-defined.
         | The error unwinds up the call stack running catch and finally
         | blocks, and if gets back to the event loop, then it's often
         | dispatched by the system to an "uncaught exception" (sync
         | context) or "unhandled rejection" (async context) handler
         | function. In NodeJS, the _default_ error handler exits the
         | process, but you can substitute your own behavior which is
         | common for long-running servers.
         | 
         | All that is to say, that _yes_ , this does work since
         | termination handler is called at the top of the stack, after
         | the stack unwinds through the finally blocks.
        
       | sylware wrote:
       | Still implemented with the super villain language, c++?
        
         | master-lincoln wrote:
         | It depends on what language the Javascript engine is
         | implemented in. For v8 that's c++ yeah. I would agree with
         | Google being a super villain nowadays, but others use c++ too
         | so I would think it's unfair to call it supervillain
         | language...
        
       | the_mitsuhiko wrote:
       | This is very useful for resource management of WASM types which
       | might have different memory backing.
        
         | jitl wrote:
         | Yeah, great for that use-case - memory management; it's great
         | to get the DisposeStack that allows "moving" out of the current
         | scope too, that's handy.
         | 
         | I adopted it for quickjs-emscripten (my quickjs in wasm thingy
         | for untrusted code in the browser) but found that differing
         | implementations between the TypeScript compiler and Babel lead
         | to it not being reliably usable for my consumers. I ended up
         | writing this code to try to work around the polyfill issues; my
         | compiler will use Symbol.for('Symbol.dispose'), but other
         | compilers may choose a different symbol...
         | 
         | https://github.com/justjake/quickjs-emscripten/blob/aa48b619...
        
       | ivan_gammel wrote:
       | Is it the same as try with resources in Java?
       | try(var reader = getReader()) {            // do read        } //
       | auto-close
        
         | masklinn wrote:
         | It's similar, but more inspired by C#'s "using declaration", an
         | evolution of the using blocks, which are the C# version of try-
         | with-resource: `using` declarations don't introduce their own
         | block / scope.
         | 
         | The original proposal references all of Python's context
         | manager, Java's try-with-resource, and C#'s using statement and
         | declaration: https://github.com/tc39/proposal-explicit-
         | resource-managemen...
        
           | OskarS wrote:
           | Only experience with C# using and Python context managers,
           | but they always seemed like such an unsatisfying solution
           | compared to C++/Rust style RAII. I mean, I get these are GC
           | languages, but why can't just this happen at end of your
           | regular scope? Why do you have to have these artificial new
           | scopes?
        
         | mattmanser wrote:
         | Yeah, as someone else has pointed out it's C# inspired, this is
         | a C# example:                   public void AMethod() {
         | //some code             using var stream = thing.GetStream();
         | //some other code             var x = thing.ReadToEnd();
         | //file will be automatically disposed as this is the last time
         | file is used             //some more code not using file
         | } //any error means file will be disposed if initialized
         | 
         | You can still do the wrap if you need more fine grained
         | control, or do anything else in the finally.
         | 
         | You can even nest them like this:                   using var
         | conn = new SqlConnection(connString);         using var cmd =
         | new SqlCommand(cmd);         conn.Open();
         | cmd.ExecuteSql();
         | 
         | Edit: hadn't read the whole article, the javascript version is
         | pretty good!
        
       | 0xCE0 wrote:
       | I don't understand how somebody can code like this and
       | reason/control anything about the program execution :)
       | 
       | async (() => (e) { try { await doSomething(); while (!done) { ({
       | done, value } = await reader.read()); } promise .then(goodA,
       | badA) .then(goodB, badB) .catch((err) => { console.error(err); }
       | catch { } finally { using stack = new DisposableStack();
       | stack.defer(() => console.log("done.")); } });
        
         | pwdisswordfishz wrote:
         | Indenting helps.
        
           | kubb wrote:
           | Also practice, programming is hard, but just because one
           | person doesn't understand something, doesn't mean it's
           | impossible or a bad idea.
        
             | exe34 wrote:
             | It's also great for job security if very few people would
             | be able to work on it.
        
             | lukan wrote:
             | But browsing the web with dev tools open, the amount of
             | error messages on allmost any site implies to me, it is
             | more than one person who doesn't understand something.
        
           | xg15 wrote:
           | Also, sticking to _one_ style and not mixing all the wildly
           | different approaches to do the same thing.
           | 
           | JS, like HTML has the special property that you effectively
           | cannot make backwards-incompatible changes ever, because that
           | scrappy webshop or router UI that was last updated in the 90s
           | still has to work.
           | 
           | But this means that the language is more like an
           | archeological site with different layers of ruins and a
           | modern city built on top of it. Don't use all the features
           | only because they are available.
        
             | wpollock wrote:
             | There used to be a great book for this, "JavaScript The
             | Good Parts". Is there a well-respected equivalent for
             | JavaScript in 2025?
        
         | gavinray wrote:
         | By programming in the language for a living and being familiar
         | with the semantics of the language's keywords -- likely the
         | same way anyone else understands their preferred language?
         | 
         | People write Haskell for a living, after all.
        
           | johnisgood wrote:
           | And Lisp, and Forth... :D
        
             | 90s_dev wrote:
             | Lisp I can understand to some degree... but writing Forth
             | for a living? I know about 20-30 languages, but _that one_
             | is Greek to me.
        
               | johnisgood wrote:
               | Stack-based or concatenative languages can be difficult
               | to understand, but as with anything, you may / could get
               | used to it. :)
               | 
               | I prefer Factor[1] over Forth, however. Maybe you'll like
               | it!
               | 
               | [1] https://factorcode.org/
        
               | 90s_dev wrote:
               | It's not that they're hard to understand, it's that
               | they're much _denser_. From Factor 's examples page:
               | 
               | > 2 3 + 4 * .
               | 
               | There's a lot more there to mentally parse than:
               | 
               | > (2 + 3) * 4
               | 
               | It's the same as when Rob Pike decries syntax
               | highlighting. No, it's very useful to me. I can read
               | _much_ quicker with it.
               | 
               | It's the same principle behind how we use heuristics to
               | much more quickly read words by sipmly looking at the
               | begninnings and ends of each word, and most of the time
               | don't even notice typos.
        
               | johnisgood wrote:
               | Well, I guess it might boil down to how one "thinks"?
               | 
               | Some people prefer:                 2 3 + 4 *
               | 
               | Some other people prefer:                 (* 4 (+ 2 3))
               | 
               | And some other people prefer:                 (2 + 3) * 4
               | 
               | I personally find the last one easier to read or
               | understand, but I have had my fair share of Common Lisp
               | and Factor. :D
               | 
               | Syntax highlighting is useful for many people, including
               | me. I can read much quicker with it, too. I know of some
               | people who write Common Lisp without syntax highlighting
               | though. :)
        
               | kazinator wrote:
               | Forth could be written devilishly where you have this
               | 2 .... hundreds of words .... +
               | 
               | where the operands of + are 2 and the result produced by
               | the hundreds of words!
               | 
               | Which could also be:                 .... hundreds of
               | words .... 2 +
               | 
               | which would be a lot easier to read!
               | 
               | If you're writing Forth, it likely behooves you to try to
               | adhere to the latter style of chaining where you take
               | everything computed thus far, and apply a small operation
               | to it with a simple operand. Not sure if it's always
               | possible:                 ... complex numerator ... ...
               | complex denominator ... /
               | 
               | Now find the division between the numerator and
               | denominator among all those words.
        
         | snickerbockers wrote:
         | That's the neat part, you don't. 90% percent of webdev is
         | "upgrading" things in ways nobody asked for or appreciates
         | because it's just taken for granted that your codebase will
         | grow mold or something if it isn't stirred often enough and the
         | other 10% of the work is fixing legitimate problems resulting
         | from the first 90%. Of course, no probability is ever actually
         | 1.0 so there will be rare occasions that you need to understand
         | something that ChatGP-err, sorry my bad, i meant to say
         | "something that you" wrote more than a year ago you suggest to
         | your boss that this bug should be preserved until next time
         | there's a new hire because it would make a great "jumping-on"
         | point and until then the users will still be able to get work
         | done by using the recommended work-around, whic his installing
         | windows XP Pirate Edition onto a VM and using IE6 to get into
         | the legacy-portal that somehow inexplicably still exists 20
         | years after the corporate merger that was supposed to make it
         | obsolete.
        
           | exe34 wrote:
           | You wrote out loud what I've been thinking quietly.
        
             | gchamonlive wrote:
             | From the lack of punctuation I think you can also rap it
             | out loud.
        
           | the_arun wrote:
           | Your paragraph is as complicated as the code we create over
           | time. Is this your point? Then I take it.
        
           | eastbound wrote:
           | Oh, we must upgrade, because of vulnerabilities. All the
           | vulnerabilities found in 90% of this moot code.
           | 
           | Ok, point taken.
        
           | lioeters wrote:
           | I fell off your train of thought about halfway through, but I
           | agree with the main point that there's way too much
           | unnecessary churn in the web dev world, 90% is about right.
           | Just busy work, changing APIs, forcing new and untested
           | paradigms onto library users, major version upgrades that
           | expect everyone to rewrite their code..
           | 
           | Intentionally or unconsciously, much of the work is about
           | ensuring there will always be demand for more work. Or else
           | there's a risk of naturally falling apart over time. Why
           | would you build it that way!?
        
         | 77pt77 wrote:
         | LLMs will do only this and you'll love it.
         | 
         | Maybe not love it, but you really won't have a choice.
        
         | stephenr wrote:
         | I mean we're talking about a language community where someone
         | created a package to tell if a variable is a number... and it
         | gets used *a lot*.
         | 
         | That JavaScript has progressed so much in some ways and yet is
         | still missing basic things like parameter types is crazy to me.
        
           | chrisweekly wrote:
           | The overwhelming majority of serious work in JS is authored
           | in TypeScript.
        
             | stephenr wrote:
             | That just Sounds like an even stronger argument to add
             | types to the language.
        
               | chrisweekly wrote:
               | Could be.
        
             | cluckindan wrote:
             | That is a "no true Scotsman" argument.
        
               | chrisweekly wrote:
               | How so? GP complained about JS lack of types. I pointed
               | out that most JS actually benefits from types, given it's
               | typically authored in TS. No moving goalposts, no "true
               | Scotsman" arg.
        
               | cluckindan wrote:
               | Paraphrasing:
               | 
               | >> JS has progressed but it still lacks types, it seems
               | crazy to do serious programming work in a language that
               | doesn't have such basic things
               | 
               | > Serious programming work in JS is done in TypeScript
        
           | cluckindan wrote:
           | Someone needs to start creating leftPad and isOdd type troll
           | packages in Rust just so we can ridicule the hubris.
        
         | notpushkin wrote:
         | To embed code on HN, add 2 or more spaces at the beginning of
         | each line:                 async (() => (e) {       try { await
         | doSomething();             while (!done) { ({ done, value } =
         | await reader.read()); }             promise
         | .then(goodA, badA)             .then(goodB, badB)
         | .catch((err) => { console.error(err); }       catch { }
         | finally { using stack = new DisposableStack();
         | stack.defer(() => console.log("done.")); }       });
         | 
         | (indentation preserved as posted by OP - I don't understand how
         | somebody can code like this either :-)
        
         | 90s_dev wrote:
         | For starters, your code is so full of _serious_ syntax errors
         | that in some places it 's not even _close_ to valid JavaScript.
         | This is my best guess reconstruction:                   (async
         | (e) => {           await doSomething()           while (!done)
         | {             ({ done, value }) = await reader.read()
         | }                promise             .then(goodA, badA)
         | .then(goodB, badB)             .catch(err => console.log(err))
         | .finally(() => {               using stack = new
         | DisposableStack()               stack.defer(() =>
         | console.log('done.'))             })         })()
         | 
         | But more importantly, this isn't even close to anything a
         | reasonable JS dev would ever write.
         | 
         | 1. It's not typical to mix await and while(!done), I can't
         | imagine what library actually needs this. You usually use one
         | or the other, and it's almost always just await:
         | await doSomething()         const value = await
         | readFully(reader)
         | 
         | 2. If you're already inside an Async IIFE, you don't need
         | promise chains. Just await the stuff as needed, unless promise
         | chains make the code shorter and cleaner, e.g.:
         | const json = await fetch(url).then(r => r.json())
         | 
         | 3. Well designed JS libraries don't usually stack promise
         | handlers like the {good,bad}{A,B} functions you implied. You
         | usually just write code and have a top level exception handler:
         | using stack = new DisposableStack()         stack.defer(() =>
         | console.log('done.'))         try {           const goodA =
         | await promise           const goodB = await goodA
         | const goodC = await goodB           return goodC         }
         | catch(e) {           myLogErr(e)         }         // finally
         | isn't needed, that's the whole point of DisposableStack
         | 
         | 4. We don't usually need AIIFEs anymore, so the outer layer can
         | just go away.
        
           | postalrat wrote:
           | By removing the semicolons you made it worse.
        
             | soulofmischief wrote:
             | That is a matter of opinion. JavaScript allows you to use
             | either convention at your preference. Personally, I feel my
             | code looks much, much cleaner without semicolons. I also
             | use whitespace liberally.
        
             | 90s_dev wrote:
             | For the longest time, I used them just in case it would
             | otherwise cause a bug. But TypeScript fully takes this into
             | account and checks for all these scenarios.
        
               | pests wrote:
               | There is only like two situations it's required in base
               | JavaScript.
        
           | TheRealPomax wrote:
           | note about that await block: "await" will await the _entire_
           | return, so if "promise" returns another promise ("goodA")
           | which in turn also returns a promise ("goodB"), which in turn
           | returns _another_ promise that ends up resolving as the non-
           | promise value "goodC", then "await promise" just... gets you
           | "goodC", directly.
           | 
           | The "example code" (if we can call it that) just used goodA
           | and goodB because it tried to make things look crazy, by
           | writing complete nonsense: none of that is necessary, we can
           | just use a single, awaiting return:                   try {
           | return await promise;         } catch(e) {
           | handleYourExceptions(e);         }
           | 
           | Done. "await" waits until whatever it's working with is no
           | longer a promise, automatically either resolving the entire
           | chain, or if the chain throws, moving us over to the
           | exception catching part of our code.
        
         | jitl wrote:
         | you can write horrid code intentionally in any programming
         | language
        
           | mrweasel wrote:
           | It just seems like it's happening way more often in
           | JavaScript, but I've seen absolute horrid and confusing
           | Python as well.
           | 
           | The JavaScript syntax wasn't great to begin with, and as
           | features are added to the language it sort of has to happen
           | within the context of what's possible. It's also becoming a
           | fairly large language, one without a standard library, so
           | things just sort of hang out in a global namespace. It's
           | honestly not to dissimilar to PHP, where the language just
           | grew more and more functions.
           | 
           | As others point out there's also some resemblance to C#. The
           | problem is that parts of the more modern C# is also a
           | confusing mess, unless you're a seasoned C# developer. The
           | new syntax features aren't bad, and developers are obviously
           | going to use them to implement all sorts of things, but if
           | you're new to the language they feel like magical
           | incantations. They are harder to read, harder to follow and
           | doesn't look like anything you know from other language. Nor
           | are they simple enough that you can just sort of accept them
           | and just type the magical number of brackets and silly
           | characters and accept that it somehow work. You frequently
           | have no idea of what you just did or why something works.
           | 
           | I feel like Javascript has reached the point where it's a
           | living language, but because of it's initial implementation
           | and inherit limits, all these great features feel misplaced,
           | bolted on and provides an obstacle for new or less
           | experienced developers. Javascript has become an enterprise
           | language, with all the negative consequences and baggage that
           | entails. It's great that we're not stuck with half a language
           | and we can do more modern stuff, it just means that we can't
           | expect people to easily pick up the language anymore.
        
             | neonsunset wrote:
             | > parts of the more modern C# is also a confusing mess
             | 
             | Do you have any examples?
        
               | mrweasel wrote:
               | For me, personally, heavy use of the => operator (which
               | happens to coinside with my main complaint of a lot
               | JavaScript code and anonymous functions). You can avoid
               | it, but is pretty standard.
               | 
               | Very specifically I also looking into JWT authentication
               | in ASP.NET Core and found the whole thing really tricky
               | to wrap my head around. That's more of a library, but I
               | think many of the usage examples ends up being a bunch of
               | spaghetti code.
        
               | neonsunset wrote:
               | Wait, what? The => operator is just for lambdas, short-
               | form returns and switch expression arms, which is as
               | common as it gets!
               | 
               | Have you never worked with any other language which lets
               | you do these?                   var say = (string s) =>
               | Console.WriteLine(s);
               | 
               | or                   struct Lease(DateTime expiration)
               | {             public bool HasExpired => expiration <
               | DateTime.UtcNow;         }
               | 
               | or                   var num = obj switch         {
               | "hello" => 42,             1337 => 41,             Lease
               | { HasExpired: false } => 100,             _ => 0
               | };
               | 
               | You'll see forms of it in practically every (good) modern
               | language. How on Earth is it confusing?
               | 
               | Authentication is generally a difficult subject, it is a
               | weaker(-ish) aspect of (otherwise top of the line)
               | ASP.NET Core. But it has exactly zero to do with C#.
        
         | TheRealPomax wrote:
         | It all starts with being well-formatted and having a proper
         | code editor instead of just a textarea on a webpage, so you'd
         | get the many error notices for that code (because it sure as
         | hell isn't valid JS =)
         | 
         | And of course, actually knowing the language you use every
         | minute of the day because that's your job helps, too, so you
         | know to rewrite that nonsense to something normal. Because
         | mixing async/await and .then.catch is ridiculous, and that
         | while loop should never be anywhere near a real code base
         | unless you want to get yelled at for landing code that seems
         | intentionally written to go into a spin loop under not-even-
         | remotely unusual circumstances.
        
         | mattlondon wrote:
         | No worse than C++, frankly.
        
       | davidmurdoch wrote:
       | I tried to write a `using` utility for JS a few years ago:
       | https://gist.github.com/davidmurdoch/dc37781b0200a2892577363...
       | 
       | It's not very ergonomic so I never tried to use it anywhere.
        
         | masklinn wrote:
         | The error was probably trying to write a generic `using`. In my
         | experience languages which use higher order functions or macros
         | for scope cleanup tend to build high-level utilities directly
         | onto the lowest level features, it can be a bit repetitive but
         | usually not too bad.
         | 
         | So in this case, rather than a generic `using` built on the
         | even more generic `try/except` you should probably have built a
         | `withFile` callback. It's a bit more repetitive, but because
         | you know exactly what you're working with it's a lot less error
         | prone, and you don't need to hope there's a ready made
         | protocol.
         | 
         | It also provides the opportunity of upgrading the entire thing
         | e.g. because `withFile` would be specialised for file
         | interaction it would be able to wrap all file operations as
         | promise-based methods instead of having to mix promises and
         | legacy callbacks.
        
           | davidmurdoch wrote:
           | Sure, the with* pattern is fine. I was just playing with the
           | idea of C#s disposable pattern in JS.
        
       | bingemaker wrote:
       | Unsure if this is inspired from C++ RAII. RAII looks very
       | elegant.
       | 
       | `[Symbol.dispose]()` threw me off
        
         | masklinn wrote:
         | > Unsure if this is inspired from C++ RAII.
         | 
         | Not really. Both are ways to perform deterministic resource
         | management, but RAII is a branch of deterministic resource
         | management which most GC'd languages can not use as they don't
         | have deterministic object lifetimes.
         | 
         | This is inspired by similar constructs in Java, C#, and Python
         | (and in fact lifted from C# with some adaptation to JS's
         | capabilities), and insofar as those were related to RAII, they
         | were a step away from it, at least when it comes to Python:
         | CPython historically did its resource management using
         | destructors which would mostly be reliably and
         | deterministically called on refcount falling to zero.
         | 
         | However,
         | 
         | 1. this was an issue for non-refcounted alternative
         | implementations of Python
         | 
         | 2. this was an issue for the possibility of an eventual (if
         | unlikely) move away from refcounting in CPython
         | 
         | 3. destructors interact in awkward ways with reference cycles
         | 
         | 4. even in a reference-counted language, destructors share
         | common finaliser issues like object resurrection
         | 
         | Thus Python ended up introducing context managers as a means of
         | deterministic resource management, and issuing guidance to
         | avoid relying on refcounting and RAII style management.
        
       | xg15 wrote:
       | This is a great idea, but:
       | 
       | > _Integration of [Symbol.dispose] and [Symbol.asyncDispose] in
       | web APIs like streams may happen in the future, so developers do
       | not have to write the manual wrapper object._
       | 
       | So for the foreseeable future, you have a situation where some
       | APIs and libraries support the feature, but others - the majority
       | - don't.
       | 
       | So you can either write your code as a complicated mix of "using"
       | directives and try/catch blocks - or you can just ignore the
       | feature and use try/catch for everything, which will result in
       | code that is far easier to understand.
       | 
       | I fear this feature has a high risk of getting a "not practically
       | usable" reputation (because _right now_ that 's what it is) which
       | will be difficult to undo even when the feature eventually has
       | enough support to be usable.
       | 
       | Which would be a real shame, as it does solve a real problem and
       | the design itself looks well thought out.
        
         | berkes wrote:
         | Isn't this typically solved with polyfills in the JavaScript
         | world?
        
           | mst wrote:
           | I regularly add Symbol based features to JS libraries I'm
           | using (named methods are riskier, of course)
           | import { SomeStreamClass as SomeStreamClass_ } from
           | "some/library"         export class SomeStreamClass extends
           | SomeStreamClass_ {           [someSymbol] (...) { ... }
           | ...         }
           | 
           | I have not blown my foot off _yet_ with this approach but,
           | uh, no warranty, express or implied.
           | 
           | It's been working excellently for me so far though.
        
             | sroussey wrote:
             | Much nicer than just adding your symbol method to the
             | original class. :p
        
         | spion wrote:
         | This is why TC39 needs to work on fundamental language features
         | like protocols. In Rust, you can define a new trait and impl it
         | for existing types. This still has flaws (orphan rule prevents
         | issues but causes bloat) but it would definitely be easier in a
         | dynamic language with unique symbol capabilies to still come up
         | with something.
        
           | jitl wrote:
           | Dynamic languages don't need protocols. If you want to make
           | an existing object "conform to AsyncDisposable", you:
           | function DisposablImageBitmap(bitmap) {
           | bitmap[Symbol.dispose] ??= () => bitmap.close()
           | return bitmap         }                  using bitmap =
           | DisposableObserver(createImageBitmap(image))
           | 
           | Or if you want to ensure _all_ ImageBitmap conform to
           | Disposable:
           | ImageBitmap.prototype[Symbol.dispose] = function() {
           | this.close() }
           | 
           | But this does leak the "trait conformance" globally; it's
           | unsafe because we don't know if some other code wants _their_
           | implementation of dispose injected to this class, if we 're
           | fighting, if some key iteration is going to get confused,
           | etc...
           | 
           | How would a protocol work here? To say something like "oh in
           | this file or scope, `ImageBitmap.prototype[Symbol.dispose]`
           | should be value `x` - but it should be the usual `undefined`
           | outside this scope"?
        
             | someothherguyy wrote:
             | Isn't disconnecting a resize observer a poor example of
             | this feature?
        
               | jitl wrote:
               | I couldn't come up with a reasonable one off the top of
               | my head, but it's for illustration - please swap in a
               | better web api in your mind
               | 
               | (edit: changed to ImageBitmap)
        
             | spion wrote:
             | You could potentially use the module system to bring
             | protocol implementations into scope. This could finally
             | solve the monkey-patching problem. But its a fairly novel
             | idea, TC39 are risk-averse, browser-side are feature-averse
             | and the language has complexities that create issues with
             | most of the more interesting ideas.
        
         | jitl wrote:
         | This is the situation in JavaScript for the last 15 years: new
         | language features come first to compilers like Babel, then to
         | the language spec, and then finally are adopted for stable APIs
         | in conservative NPM packages and in the browser. The process
         | from "it shows up as a compiler plugin" to "it's adopted by
         | some browser API" can often be like 3-4 years; and even after
         | it's available in "evergreen" browsers, you still need to have
         | either polyfills or a few more years of waiting for it to be
         | guaranteed available on older end-user devices.
         | 
         | Developers are quite used to writing small wrappers around web
         | APIs anyways since improvement to them comes very slowly, and a
         | small wrapper is often a lesser evil compared to polyfills; or
         | the browser API is just annoying on the typical use path so of
         | course you want something a little different.
         | 
         | At least, I personally have never seen a new langauge feature
         | that seems useful and thought to myself "wow this is going to
         | be hard to use"
        
         | MrJohz wrote:
         | In practice, a lot of stuff has already implemented this using
         | forwards-compatible polyfills. Most of the backend NodeJS
         | ecosystem, for example, already supports a lot of this, and you
         | have been able to use this feature quite effectively for some
         | time (with a transpiler to handle the syntax). In fact, I gave
         | a couple of talks about this feature last year, and while
         | researching for them, I was amazed by how many APIs in NodeJS
         | itself or in common libraries already supported Symbol.dispose,
         | even if the `using` syntax wasn't implemented anywhere.
         | 
         | I suspect it's going to be less common in frontend code,
         | because frontend code normally has its own lifecycle/cleanup
         | management systems, but I can imagine it still being useful in
         | a few places. I'd also like to see a few more testing libraries
         | implement these symbols. But I suspect, due to the prevalence
         | of support in backend code, that will all come with time.
        
         | TheRealPomax wrote:
         | > So for the foreseeable future, you have a situation where
         | some APIs and libraries support the feature, but others - the
         | majority - don't.
         | 
         | Welcome to the web. This has pretty much been the case since
         | JavaScript 1.1 created the situation where existing code used
         | shims for things we wanted, and newer code didn't because it
         | had become part of the language.
        
         | bakkoting wrote:
         | For APIs which don't support this, you can still use `using` by
         | using DisposableStack:                   using disposer = new
         | DisposableStack;         const resource = disposer.adopt(new
         | Resource, r => r.close());
         | 
         | This is still simpler than try/catch, especially if you have
         | multiple resources, so it can be adopted as soon as your
         | runtime supports the new syntax, without needing to wait for
         | existing resources to update.
        
       | mst wrote:
       | If you want to play with this, Bun 1.0.23+ seems to already have
       | support: https://github.com/oven-sh/bun/discussions/4325
        
       | spelley wrote:
       | This looks most similar to golang's defer. It runs cleanup code
       | when leaving the current scope.
       | 
       | It differs from try/finally, c# "using," and Java try-with-
       | resources in that it doesn't require the to-be-disposed object to
       | be declared at the start of the scope (although doing so arguably
       | makes code easier to understand).
       | 
       | It differs from some sort of destructor in that the dispose call
       | is tied to scope, not object lifecycle. Objects may outlive the
       | scope if there are other references, and so these are different.
       | 
       | If you like golang's defer then you might like this.
        
         | masklinn wrote:
         | > This looks most similar to golang's defer. It runs cleanup
         | code when leaving the current scope.
         | 
         | It's nothing like go's defer: Go's defer is function-scoped and
         | registers a callback, using is block-scoped and registers an
         | object with a well defined protocol.
         | 
         | > It differs from [...] c# "using,"
         | 
         | It's pretty much a direct copy of C#'s `using` declaration (as
         | opposed to the using statement):
         | https://learn.microsoft.com/en-us/dotnet/csharp/language-
         | ref....
         | 
         | This can also be seen from the proposal itself
         | (https://github.com/tc39/proposal-explicit-resource-
         | managemen...) which cites C#'s using statement and declaration,
         | Java's try-with-resource, and Python's context managers as
         | _prior art_ , but only mentions Go's defer as something you can
         | emulate via DisposableStack and AsyncDisposableStack (types
         | which are specifically inspired by Python's ExitStack),
        
       | jmull wrote:
       | I would have preferred "defer", but "using" is a lot better than
       | nothing.
        
         | 90s_dev wrote:
         | Using is more flexible, since it doesn't need a function call,
         | but can simply assign a variable that implements [[dispose]]
        
           | masklinn wrote:
           | They're basically dual of one another.
           | 
           | `using` is mostly more convenient, because it registers
           | cleanup without needing extra calls, unlike `defer`.
           | 
           | And of course you can trivially bridge callbacks, either by
           | wrapping a function in a disposeable literal or by using the
           | DisposableStack/AsyncDisposableStack utility types which the
           | proposal also adds.
        
           | jmull wrote:
           | Only when you have an object that implements
           | [Symbol.dispose]. If you don't, then you need to create one
           | (like the wrapper in the example from the article) or bang
           | out some boilerplate to explicitly make and use a
           | DisposableStack().
           | 
           | So with using there's a little collection of language
           | features to learn and use, and (probably more importantly),
           | either app devs and library devs have to get on the same page
           | with this at the same time, or app devs have to add a handful
           | of boilerplate at each call site for wrappers or
           | DisposableStacks.
        
       | mst wrote:
       | Oh my word.                   const defer = f => ({
       | [Symbol.dispose]: f })              using defer(() => cleanup())
       | 
       | That only just occurred to me. To everybody else who finds it
       | completely obvious, "well done" but it seemed worthy of mention
       | nonetheless.
        
         | masklinn wrote:
         | Note that depending on use case, it may be preferrable to use
         | `DisposableStack` and `AsyncDisposableStack` which are part of
         | the `using` proposal and have built-in support for callback
         | registration.
         | 
         | This is notably necessary for scope-bridging and conditional
         | registration as `using` is block-scoped so                   if
         | (condition) {             using x = { [Symbol.dispose]: cleanup
         | }         } // cleanup is called here
         | 
         | But because `using` is a variant of `const` which requires an
         | initialisation value which it registers immediately this will
         | fail:                   using x; // SyntaxError: using missing
         | initialiser         if (condition) {             x = {
         | [Symbol.dispose]: cleanup };         }
         | 
         | and so will this:                   using x = {
         | [Symbol.dispose]() {} };         if (condition) {
         | // TypeError: assignment to using variable             x = {
         | [Symbol.dispose]: cleanup }         }
         | 
         | Instead, you'd write:                   using x = new
         | DisposableStack;         if (condition) {
         | x.defer(cleanup)         }
         | 
         | Similarly if you want to acquire a resource in a block
         | (conditionally or not) but want the cleanup to happen at the
         | function level, you'd create a stack at the function toplevel
         | then add your disposables or callbacks to it as you go.
        
         | mattlondon wrote:
         | Just like golang. Nice.
        
       | 90s_dev wrote:
       | When I discovered this feature, I looked everywhere in my
       | codebases for a place to use it. Turns out most JS APIs, whether
       | Web or Node.js, just don't need it, since they auto-close your
       | resources for you. The few times I did call .close() used
       | callbacks and would have been less clean/intuitive/correct to
       | rewrite as scoped. I haven't yet been able to clean up even one
       | line of code with this feature :(
        
         | baq wrote:
         | If you're using the withResource() pattern, you're already
         | effectively doing this, so yeah. If you're using try/finally,
         | it might be worth taking a second look.
        
         | neRok wrote:
         | I'm just a hobbyist and have some scriplets I've written to
         | "improve" things on various websites. As part of that I needed
         | an uninstall/undo feature - so with my "bush league" code I
         | would do `window.mything = {something}`, and based upon
         | previous dabblings with the likes of python/c#/go, I presumed I
         | would be able to do `delete window.mything` and it would _auto-
         | magically_ call a function I would have written to do the work.
         | So the new `[Symbol.dispose]()` feature /function would have
         | done what I was looking for - but really, it;s not a big deal,
         | because all that I actually had to do was write an interface
         | spec'ing `{}.remove()` method, and call it where needed.
         | 
         | (This paragraph is getting off topic, but still... ) Below is
         | my exact interface that I have in a .d.ts file. The reason for
         | that file is because I like typed languages (ie TypeScript),
         | but I don't want to install stuff like node-js for such simple
         | things. So I realised vscode can/will check js files as ts on-
         | the-go, so in a few spots (like this) I needed to "type"
         | something - and then I found some posts about svelte source
         | code using js-docs to type their code-base instead of
         | typescript. So that's basically what I've done here...
         | export global {         interface Window {
         | MyThing?: {remove: ()=>any}         }       }
         | 
         | So chances are that in the places you _could_ use this feature,
         | you 've probably already got an "interface" for closing things
         | when done (even if you haven't defined the interface in a type
         | system).
        
         | morsecodist wrote:
         | It doesn't seem to be widely used yet. I used it to clean up
         | temporary files for a loader that downloads them.
        
       | taylorallred wrote:
       | Ngl, I was hoping "resources" was referring to memory.
        
         | tempaccount420 wrote:
         | Would be amazing to have a low-level borrow-checked subset of
         | JS, as part of JS, so you can rewrite your hot loops in it.
         | 
         | Granted, you could also just import * from './low-level.wat'
         | (or .c, and compile it automatically to WASM)
        
       | qudat wrote:
       | Resource management, especially when lexical scoping is a
       | feature, is why some of us have been working on bringing
       | structured concurrency to JS: https://bower.sh/why-structured-
       | concurrency
       | 
       | Library that leverages structured concurrency:
       | https://frontside.com/effection
        
       | vaylian wrote:
       | I understand that JavaScript needs to maintain backwards
       | compatibility, but the syntax
       | 
       | [Symbol.dispose]()
       | 
       | is very weird in my eyes. This looks like an array which is
       | called like a function and the array contains a method-handle.
       | 
       | What is this syntax called? I would like to learn more about it.
        
         | homebrewer wrote:
         | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
         | 
         | Someone more knowledgeable will join in soon, but I'm pretty
         | sure it was derived from:                 const x = { age: 42
         | };       x[Symbol.name] = "joe"; // <--- this
         | 
         | so it makes a lot of sense.
        
         | Kalabasa wrote:
         | Object property access i guess. Like
         | 
         | myObj["myProperty"]
         | 
         | If it's a function then it could be invoked,
         | 
         | myObj["myProperty"]()
         | 
         | If the key was a symbol,
         | 
         | myObj[theSymbol]()
        
           | TheRealPomax wrote:
           | pretty sure they were asking about the dynamic property name,
           | { [thing]: ... }
        
         | MrJohz wrote:
         | Dynamic property access perhaps?
         | 
         | The premise is that you can always access an object's
         | properties using indexing syntax as well as the normal dot
         | syntax. So `object.foo` is the equivalent of `object["foo"]` or
         | `object["f" + "o" + "o"]` (because the value inside the square
         | brackets can be any expression). And if `object.foo` is a
         | method, you can do `object.foo()` or `object ["foo"]()` or
         | whatever else as well.
         | 
         | Normally, the key expression will always be coerced to a
         | string, so if you did `object[2]`, this would be the equivalent
         | of object["2"]. But there is an exception for symbols, which
         | are a kind of unique object that is always compared by
         | reference. Symbols can be used as keys just as they are, so if
         | you do something like                   const obj = {}
         | obj.foo = "bar"         obj[Symbol("foo")] = "bar"
         | console.log(obj)
         | 
         | You should see in the console that this object has a special
         | key that is a symbol, as well as the normal "foo" attribute.
         | 
         | The last piece of the puzzle is that there are certain "well
         | known symbols" that are mostly used for extending an object's
         | behaviour, a bit like __dunder__ methods in Python.
         | Symbol.dispose is one of these - it's a symbol that is globally
         | accessible and always means the same thing, and can be used to
         | define some new functionality without breaking backwards
         | compatibility.
         | 
         | I hope that helps, feel free to ask more questions.
        
           | cluckindan wrote:
           | It's not that, it's a dynamic key in an object literal.
           | const key = "foo";         const obj = { [key]: "bar" };
           | console.log(obj.foo); // prints "bar"
        
             | MrJohz wrote:
             | That's also possible, and it's common when using this
             | pattern, but the specific syntax in the original question
             | was I believe property access, and not part of a property
             | literal. I didn't bring that up because I thought my
             | comment was long enough and I wanted to explain that
             | specific syntax. But yeah, you also have this syntax to set
             | properties in object literals, and a similar syntax in
             | classes.
        
         | ff2400t wrote:
         | This syntax has been used for quite some time. JavaScript
         | iterator use the same syntax and they been part of JavaScript
         | for almost a decade now.
        
         | zdragnar wrote:
         | Dynamic keys (square brackets on the left hand side in an
         | object literal) have been around for nearly 10 years, if memory
         | serves.
         | 
         | https://www.samanthaming.com/tidbits/37-dynamic-property-nam...
         | 
         | Also in the example is method shorthand:
         | 
         | https://www.samanthaming.com/tidbits/5-concise-method-syntax...
         | 
         | Since symbols cannot be referred to by strings, you can combine
         | the two.
         | 
         | Basically, there isn't any new syntax here.
        
           | a4isms wrote:
           | Yes, this will be familiar to people creating objects or
           | classes that are intended to represent iterable collections.
           | You do the same dynamic key syntax with a class declaration
           | or object literal, but use `Symbol.iterator` as the well-
           | known symbol for the method.
           | 
           | https://developer.mozilla.org/en-
           | US/docs/Web/JavaScript/Refe...
        
         | 90s_dev wrote:
         | const o = {}       o["foo"] = function(){}       o["foo"]()
         | let key = "foo"       o[key]()       key = Symbol.dispose ??
         | Symbol.for('dispose')       o[key]()       o[Symbol.dispose]()
        
         | mceachen wrote:
         | Other posters correctly described _what_ this is, but I didn't
         | see anyone answer _why_.
         | 
         | Using a Symbol as the method name disambiguates this method
         | from any previously-defined methods.
         | 
         | In other words, by using a Symbol for the method name (and not
         | using a string), it's impossible to "name collide" on this new
         | API, which would accidentally mark a class as disposable.
        
       | pier25 wrote:
       | did this go through the TC39 or is this a V8 only feature?
        
         | bri3d wrote:
         | https://github.com/tc39/proposal-explicit-resource-managemen...
        
           | pier25 wrote:
           | thanks!
        
         | k__ wrote:
         | Bun seems to have it and it's not using V8.
        
       | morsecodist wrote:
       | I just wrote a blog post (https://morsecodist.io/blog/typescript-
       | resource-management) about this feature. I love it and I feel it
       | still hasn't caught on in the ecosystem.
        
       | nayuki wrote:
       | This proposal reeks of "What color is your function?"
       | https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... .
       | The distinction between sync functions and async functions keeps
       | intruding into every feature. As we can see here, there are
       | Symbol.dispose and Symbol.asyncDispose, and DisposableStack and
       | AsyncDisposableStack.
       | 
       | I am so glad that Java decided to go down the path of virtual
       | threads (JEP 444, JDK 21, Sep 2023). They decided to put some
       | complexity into the JVM in order to spare application developers,
       | library writers, and human debuggers from even more complexity.
        
         | jupp0r wrote:
         | I disagree. Hiding async makes reasoning about code harder and
         | not easier. I want to know whether disposal is async and
         | potentially affected by network outages, etc.
        
         | timewizard wrote:
         | In plain javascript it's not a problem. Types are ducked so if
         | you receive a result or a promise it doesn't matter. You can
         | functionally work around the "color problem" using this
         | dynamism.
         | 
         | It's only when you do something wacky like try to add a whole
         | type system to a fully duck typed language that you run into
         | problems with this. Or if you make the mistake of copying this
         | async/await mechanism and then hamfistedly shove it into a
         | compiled language.
        
           | rtpg wrote:
           | Typescript has no problem with typing your scenario (or at
           | least nothing that isn't present in "plain" JavaScript...
           | what if your value is a promise?)
           | 
           | And compiled languages don't have more trouble with this than
           | JavaScript. Or rather, Javascript doesn't have less issues on
           | this front. The color issue is an issue at the syntactic
           | level!
        
         | vips7L wrote:
         | Beyond happy Java made that decision as well.
        
         | anon291 wrote:
         | This is because normal execution and async functions form
         | distinct closed Cartesian categories in which the normal
         | execution category is directly embeddable in the async one.
         | 
         | All functions have color (i.e. particular categories in which
         | they can be expressed) but only some languages make it
         | explicit. It's a language design choice, but categories are
         | extremely powerful and applicable beyond just threading. Plus,
         | Java and thread based approaches have to deal with
         | synchronization which is ... Difficult.
         | 
         | (JavaScript restricts itself to monadic categories and more
         | specifically to those expressible via call with continuation
         | essentially)
        
         | rtpg wrote:
         | It's so annoying that right now the state of the art is
         | essentially "just write all of your code as async because even
         | sync callers could just spin up a one-off event loop in the
         | worst case" in most languages.
         | 
         | The only language I know that navigates this issue well is
         | Purescript, because you can write code that targets Eff (sync
         | effects) or Aff (async effects) and at call time decide.
         | 
         | Structured concurrency is wonderful, but my impression is we're
         | doing all this syntactic work not to get structured
         | concurrency, but mostly to have, like, multiple top-level
         | request handlers in our server. Embarassingly parallel work!
        
         | olalonde wrote:
         | I'm not sure how it's implemented in the JVM, but in general,
         | multithreading is notoriously difficult to reason about. Entire
         | books are devoted to the pitfalls (race conditions, deadlocks,
         | livelocks, starvation, memory visibility issues, and more).
         | Compared to that, single threaded async programming is a walk
         | in the park. I'd rather deal with "function color" than try to
         | debug a heisenbug in a multithreaded app.
        
           | pton_xd wrote:
           | Share-nothing via message passing ala Erlang makes
           | multithreading much more tractable. I daresay it's enjoyable.
           | 
           | Async is a decent syntax for simple tasks but that simplicity
           | falls apart when composing larger structures and dealing with
           | error handling and whatnot. I find it more difficult to
           | understand what's going on compared to explicit threading.
        
             | olalonde wrote:
             | > Async is a decent syntax for simple tasks but that
             | simplicity falls apart when composing larger structures and
             | dealing with error handling and whatnot.
             | 
             | Do you have a concrete example? It has just never really
             | been an issue for me since async/await (callback hell was a
             | thing though).
        
       | senfiaj wrote:
       | I wrote an article with more examples
       | https://waspdev.com/articles/2025-05-17/js-destructors-or-ex... .
       | It's actually a simplified version of this
       | https://github.com/tc39/proposal-explicit-resource-managemen... .
        
       ___________________________________________________________________
       (page generated 2025-05-17 23:00 UTC)