JavaScript's New Superpower: Explicit Resource Management
Why not destructors / GC-based cleanup?
- Thread repeatedly stresses that GC-tied destructors are non-deterministic in modern GC’d languages.
- Finalizers (WeakRef / FinalizationRegistry) exist but are considered unpredictable, engine-dependent, and discouraged for normal cleanup.
- Lexical “using” cleanup is deterministic: runs when the block completes (normal return, throw, break, etc.), so you can rely on locks/files/resources being released before leaving a scope.
- RAII-style “destroy on last reference” is seen as incompatible with advanced, non-reference-counting GCs.
Symbols and protocol design
[Symbol.dispose]/[Symbol.asyncDispose]continue the “well-known symbols” pattern (like[Symbol.iterator]): a protocol mechanism that can’t collide with existing string-named methods.- Proposals for a
disposekeyword or aResourcebase class are criticized as brittle (name collisions, awkward inheritance). - Some find the syntax ugly/confusing; others note computed property names and symbol keys have been standard ES features for ~a decade.
Sync vs async disposal (“coloring”)
- Parallel sync/async hooks (dispose vs asyncDispose, DisposableStack vs AsyncDisposableStack) are seen by some as another instance of the “function color” problem.
- Critics wish async-ness were handled by the runtime/type system rather than duplicated APIs; supporters argue being explicit about async disposal is important for reasoning about network or I/O–bound cleanup.
Comparisons to other languages
- Feature is widely recognized as lifted from C#’s
usingdeclaration andIDisposable/IAsyncDisposable. - Also compared to Java try-with-resources, Python context managers / ExitStack, and Go’s
defer(via DisposableStack). - Multiple comments note this is explicitly not RAII; it’s scope-based cleanup in a GC language.
Error-proneness and tooling
- Main risks:
- Using
let/constinstead ofusingsilently leaks resources. - Composite objects must remember to dispose children.
- Using
- Several expect TypeScript + eslint rules to detect undisposed resources and misuses based on the standardized symbols.
- Discussion of subtle patterns around ownership, fields, double-dispose, and the need for analyzers (with C#’s experience as precedent).
Syntax, ergonomics, and alternatives
- Debate over
using x = …;vs a block formusing (const x = …) { … }, and lack of destructuring. - Supporters like that
usingdoesn’t force an extra nested scope and can be combined with simple{ … }blocks when needed. DisposableStack/AsyncDisposableStackhighlighted as the right tool for:- Bridging callback-based cleanup (
defer(fn)style). - Conditional registration and scope-bridging.
move()–style transfer of ownership out of a constructor or inner scope.
- Bridging callback-based cleanup (
Adoption and applicability
- Concern: partial ecosystem support means mixed
usingandtry/finallyfor a while; some fear it’ll be seen as “not practically usable.” - Others note many Node/back-end libraries already polyfill
Symbol.dispose, so the syntax can be adopted early via transpilers. - Use cases emphasized: WASM resource lifetimes, Unity/JS bridges, streams, temp files, DB connections, long-lived browser tabs where leaks matter.
Broader JS language evolution
- Some see this as much-needed standardization of an everyday pattern (like context managers); others as continued accretion of complex, C#-style features onto an already large, “archaeological” language.
- A minority argue that such complexity pushes them toward languages like Rust or toward a typed, non-JS language for the web.