Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



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"


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.


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.


Isn't this typically solved with polyfills in the JavaScript world?


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.


Much nicer than just adding your symbol method to the original class. :p


13 days late but for posterity:

Yes. Not Wanting To Do That was the motivating factor for coming up with this approach :D


I guess it could be improved with a simple check if SomeStreamClass_ already has someSymbol and then raise an exception, log a warning or some such.


8 days late but for posterity:

So far I've only ever been using a private symbol that only exists within the codebase in question (and is then exported to other parts of said codebase as required).

If I ever decide to generalise the approach a bit, I'll hopefully remember to do precisely what you describe.

Possibly with the addition of providing an "I am overriding this deliberately" flag that blows up if it doesn't already have said symbol.

But for the moment, the maximally dumbass approach in my original post is DTRT for me so far.


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.


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"?


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.


Isn't disconnecting a resize observer a poor example of this feature?


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)


> 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: