TypeScript 5.2's new keyword: 'Using'
totaltypescript.com
totaltypescript.com
But as to your question "Could anyone here help me understand a more robust example where `try...finally` just won't work", using is just basically syntactic sugar for try/finally, but I'm very much in favor of syntax that gets rid of lots of verbose boilerplate that can obscure the real purpose of your code.
[1] https://github.com/tc39/proposal-explicit-resource-managemen...
That doesn’t appear to be the case, a resource can be returned, but this is block scoped. It appears to be closer to a using statement in C#.
Now you can just call “await using foo(…)”, rather than the try/finally pattern, and get the automatic dispose call on leaving scope.
Kotlin has “use” with a similar effect, and i much prefer it over try/finally. Might be worth checking it out too.
In the context of the parent question then it's the consistent standard syntax that makes things easier to read - something that indeed is impossible to see with an isolated example that, if anything, may look less clear than the syntax you're used to.
The reality is, there’s no better option at the moment.
Virtually every other ecosystem has concluded globals are not the best practice. (At least until we return to dependency injection containers where they are suddenly cool again but I digress.)
Singletons are bad in complicated, long-running processes, because you're in trouble if you want to have more than one of something, and cleanup can be a problem. A one-to-one relationship with the running process is problematic.
But JavaScript often runs in a disposable runtime environment that forces cleanup when terminated. For example, a web page or a web worker. Memory leaks usually aren't a problem and you can just treat it like arena allocation.
If you want more than one web page, it's very easy to do.
Similarly, if you're writing scripts using disposable Unix processes then a memory leak in a command isn't all that big a deal; you can sometimes get away with never freeing anything because the OS will do it.
As with everything, there is a time and a place, but if you understand the tradeoffs to recognize that time and place you won't be soliciting random advice from the internet, and thus won't hear the 'good'.
I get the impression that the JavaScript world largely doesn't care much for testing, though.
It’s pretty simple, but it has solved the testing problem for me because within that getResource function I can check if the environment being ran in is a test environment. If so, return a mocked instance. If not, return the real instance.
It’s pretty rudimentary, but it’s solved our issues.
For the browser context, I don’t have a good use case for ‘using’ (except for browser devs themselves where some code could be in JS now but impossible before).
Then again, people always surprise you with new use cases!
It appears all this does is avoids needing to manually call close (or equivalent)? While that is a nice addition, helping to avoid the situation where you forget, why does globals become the alternative? Isn't simply calling close manually the best option at the moment?
In this (still contrived) example, we end up having to do nested try/finally blocks.
Before:
let totalSize = 0;
let fileListHandle;
try {
fileListHandle = await open("file-list.txt", "r");
for await (const line of fileListHandle.readLines()) {
let lineFileHandle;
try {
lineFileHandle = await open(lineFileHandle, "r");
totalSize += await lineFileHandle.read().bytesRead;
} finally {
await lineFileHandle?.close();
}
}
} finally {
await fileListHandle?.close();
}
console.log(totalSize);
After: let totalSize = 0;
try {
await using fileListHandle = getFileHandle("file-list.txt", "r");
for await (const line of fileListHandle.readLines()) {
await using lineFileHandle = getFileHandle(lineFileHandle, "r");
totalSize += await lineFileHandle.read().bytesRead;
}
}
console.log(totalSize);It’s possible that there are UI systems where this would work but not the ones you listed above.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
With this new using syntax, resources are disposed of when the object they are tied to goes out of _lexical scope_, which doesn’t need to worry about the runtime or object lifetimes at all. This example from the TC39 proposal makes it pretty clear:
function * g() {
using handle = acquireFileHandle(); // block-scoped critical resource
} // cleanup
{
using obj = g(); // block-scoped declaration
const r = obj.next();
} // calls finally blocks in `g`
https://github.com/tc39/proposal-explicit-resource-managemen... > if the above function returned a database connection to the Nodejs main loop, which stuffed it into a pool array of connections, wouldn't that still remain in scope for the remainder of the program unless it were explicitly deleted?
No, since these are block-scoped the original variable goes out of scope when the block it was declared in ends. The underlying _value_ that the variable is a reference to certainly can escape the block in a number of ways (assignments to existing variables or properties, closures), but this system doesn’t care about any of that, it’s directly equivalent to using try and finally, and finally blocks execute when you would expect them to. lineFileHandle = await open(lineFileHandle, "r");
should be lineFileHandle = await open(line, "r");
Same thing for the using version. let totalSize = 0;
const fileListHandle = await open("file-list.txt", "r");
try {
for await (const line of fileListHandle.readLines()) {
const lineFileHandle = await open(lineFileHandle, "r");
try {
totalSize += await lineFileHandle.read().bytesRead;
} finally {
await lineFileHandle.close();
}
}
} finally {
await fileListHandle.close();
}
console.log(totalSize);Don't get me wrong there are alot of great things in ES6, but it was not quite the same language after...
Most programming languages are just different syntax on top of the same core primitives.
This is a bit of weird assignment, given that the Class syntactic sugar over Prototype was one of the marquee features of that release.
Ascribing that kind of motive to something as innocuous as syntactic sugar is silly at best. Most syntactic sugar I've seen is pretty clearly an attempt to make users' lives easier (whether successful or not is irrelevant).
Source: am an actual language designer.
Now it's "JavaScript used to be a little elegant". Fantastic.
More seriously:
1. This is typescript. Feel free to use js without it. (EDIT: This is wrong :)
2. This is a relatively easy feature, and there are equivalent features in some other mainstream languages.
You don't have to like it, I'm not sure I do, but this feels so dramatic.
Sir.
Wat. [0]
I could not possibly imagine a sentence I would be so vehemently disagreeing with.
The "best" way to do this in JS today is to have one function that opens and cleans up the input file (with a try/finally). That function calls another that opens and cleans up the db connection in the same way, and so on. That's verbose and makes your code nonlinear.
The new keyword brings Go's (and other languages') equivalent of 'defer' to JS. You don't need to worry about cleaning up, the 'using' keyword implies that it happens for you.
One way is to use object pooling, but doing so in JS can be brittle because you have to remember to manually call `Pool.release(obj)`, `obj.free()` or whatever method you’ve chosen to return an object to the pool.
If a developer forgets to do this, you could exhaust the object pool, or if it’s growable, cause a memory leak! In a game’s update loop that could happen very quickly.
With this new feature, you could grab a short-lived object from the pool and automatically return it to the pool at the end of the method or loop.
Example - imagine this is inside an update method called 60 times per second:
for (const enemy of enemies) {
using pos = Pool.getVec3();
// do stuff with pos
enemy.setPosition(pos);
} // pos is returned to pool automatically
You asked about try/catch/finally. The downsides for this use-case are:* Big performance hit when you use it in a hot loop like this - the disposal could be happening ~10,000 times per second.
* Harder to remember to fill all your loops with try…finally, ugly to have double braces anytime you’re using a pooled object.
* It’s an abuse of syntax if you’re not actually catching any error.
https://github.com/tc39/proposal-explicit-resource-managemen...
It’s a shame the author insisted on it looking like C# and sabotaged attempts to combine disposal with destructuring.
Also, there is no move operator without futzing around with DisposableStack. Though that could be introduced later; it’s not a day-zero design issue.
What's the not-C#-like method that would have allowed destructuring in a better way?
Can you explain by what you mean by this? What would "disposal combined with destructuring" look like?
Can you elaborate more of what combining disposal and destructuring would look like?
using (x = ...) {
console.log(x.whatever)
}
Thoughts? I feel like the current proposal has a lot of the same issues I dislike with C++ (a focus on "terser" syntax obscures what is actually happening e.g. initializer lists don't actually tell you what constructor they're calling). using(..., x => {
console.log(x.whatever)
});Granted it still could get screwed up. But if it doesn't, and assuming of course that I've understood it correctly to begin with, I suspect a comfortable way to think about it will be as a means of hinting to the GC how it should handle cases where free() alone doesn't suffice.
The annoyance to me is that like all scope / defer constructs it gets wonky when paths split between a success and a failure path, and you do want the resource to escape in the former case. That's an area where RAII really shines.
Is this even supported by the proposal in question though? I don't see anything here that indicates any sort of escape analysis or object tracking is performed—resources are always cleaned up lexically so `using foo = handle(); return foo` will always call foo to be cleaned up, just like the equivalent `finally` statement.
using stack = new DisposableStack();
stack.use(resource());
if (condition) {
// the resources will _not_ get disposed automatically by this return
// it's the caller's responsibility to call `stack.dispose()`
return stack.move();
}No and that’s exactly the problem.
If you “using” a resource and you leak it, you’ve now handed off a disposed off and probably useless resource.
If you don’t, and don’t, you have a resource leak.
In trivial cases this is fine-ish, just do whichever is needed, but when there are conditional paths internally (sometimes you signal an error others you return normally) you can’t “using” at all, you’re back to try/except and disposing by hand.
RAII does not have that issue, it does the right thing every time.
A few languages solve this by having additional constructs for these specific scenario (e.g. errdefer), though that requires more direct integration with the language’s error reporting, and thus forbids (or at least does not work with) alternate ones.
What's missing from the article is the semantics when there is an error. In Python, `__exit__` is always called (with the exception as param), but depending of if you return True or False, you reraise or not the exception.
Anyway, it's a useful addition.
While you can craft an API with this behavior in JS by accepting two callbacks, it's nice to have a standardized way to do it. Plus making it an official feature will encourage the pattern, which nurture the culture for better API.
{
await using { connection } = getConnection();
// Do stuff with connection
} // Automatically closed!
The object returned by getConnection() is destructured to extract the field named connection and assign it to a local variable with the same name. The containing object presumably continues to exist anonymously so its Symbol.asyncDispose function will still be called when it goes out of scope.Can anyone explain which language rule guarantees this behavior in C# and in TypeScript?
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYAMBYAUBgRj2Nw...
I think that records don't generate a deconstruct method when they only have one property, but even if you manually define one you'll get an error on `var (varName) = ...`
It makes me realize I've never really considered the memory implications of doing something like:
const { someSmallPart } = getSomeMassiveObject();
I don't really know the lifetime of "massive object". Probably a bad on my part for not knowing if Javascript garbage collection is allowed to notice that the only thing with a reference is `someSmallPart` and could therefore release any other parts of the massive object. If the answer to that is "no" then there is no problem with the above pattern.If the answer is "yes", then things could get complicated. e.g.
async function getComplicatedObject() {
const db = await db.getConnection( ... );
const f = await file.open( ... );
return { db, f, [Symbol.asyncDispose]: () => {
db.close()
f.close()
}
}
{
await using { f } = getComplicatedObject();
// ... more stuff with awaits where the GC might decide `db` isn't used so it can clean
} // I would not expect a null ref error on `db` here when `asyncDispose` is called
I mean, you can handle the above even if the GC is allowed to be smart and discard `db` - it just makes it significantly more complicated and requires clever book-keeping.1. https://github.com/tc39/proposal-explicit-resource-managemen...
But assuming a javascript implementation where it works, I don't know what you mean by "language rule". The spec says what "using" means, and it doesn't require any behavior the language doesn't already have. The old spec said "When try using is parsed with an Expression, an implicit block-scoped binding is created for the result of the expression.", and the new one says "When a using declaration is parsed with BindingIdentifier Initializer, the bindings created in the declaration are tracked for disposal at the end of the containing Block or Module". Then the spec has an example implementation written in javascript, with the value being added to a (non-user-accessible) list. https://github.com/tc39/proposal-explicit-resource-managemen...
Is that the language rule you wanted?
Here are common pitfalls: https://auth0.com/blog/four-types-of-leaks-in-your-javascrip...
A more useful definition is memory that remains allocated unintentionally.
If your program continuously allocates memory that it doesn't free and doesn't serve a useful purpose, it doesn't really matter if it's, e.g., being referenced by some cache or just completely unreferenced -- it's still causing the same problem and has the same solution (that is, release the memory when you're done with it -- whether that's by calling "free" or clearing the reference, or whatever your memory management system has)
For example, if you're using three.js, various WebGL resources need to be manually disposed: https://threejs.org/docs/index.html#manual/en/introduction/H...
Many languages have tried to attach "disposers" to garbage collection events. The more common term in this case is "finalizer". As far as I know, no language has ever had a general-purpose, robust solution that works so well that it can just be used. Many have tried, and they all end up either deprecated, determined by the community to be a footgun, or backed down to some specialized use case (like picking up after file handles that are GC'd but the expected thing is still that normal user code closes them and this is explicitly pitched as an undesirable fallback, not the thing you should do).
I assume there would be similar logic for JavaScript but I haven't looked at the specification.
It is not always a mistake to miss the using keyword -- if you're taking ownership of the object, then you will dispose of it manually (potentially in your own dispose method).
Even better, right now lint tools have to individually case rules specific to each library given the variety of names, but lint tools can make a nice global rule for all objects implementing [Symbol.dispose]() (just as they can warn when a Promise is left unawaited).
Probably it will block the pattern that `call a function that returns disposable object without using using keyword`...etc.
Short answer: Yes, Disposable can leak if you forget "using" it. And it will leak if the Disposable is not guarded by advanced GC mechanisms like the FinalizationRegistry.
Unlike C# where it's relatively easier to utilize its GC to dispose undisposed resources [2], properly utilizing FinalizationRegistry to do the same thing in JavaScript is not that simple. In response to our conversation, Ron is proposing adding the use of FinalizationRegistry as a best practice note [3], but only for native handles. It's mainly meant for JS engine developers.
Most JS developers wrapping anything inside a Disposable would not go through the complexity of integrating with FinalizationRegistry, thus cannot gain the same level of memory-safety, and will leak if not "using" it.
IMO this design will cause a lot of problems, misuses and abuses. But making JS to look more like C# is on Microsoft's agenda so they are probably not going to change anything.
[1]: https://github.com/tc39/proposal-explicit-resource-managemen...
[2]: https://stackoverflow.com/a/538238/1481095
[3]: https://github.com/tc39/proposal-explicit-resource-managemen...
let connection = getConnection()
defer connection.dispose()
...
being equivalent to let connection = getConnection()
try {
...
} finally {
connection.dispose()
}
"defer" unlike "using" does not require a specified entry point method, so no [Symbol.dispose] required.acquireX/releaseX maps nicely to lexical scope and adding a language feature to provide sugar for it is pretty nice, imo/e. Defer doesn't really "get it."
The core value prop of both “using” or “defer” is that a single scope can manage the lifetime of some resource while child scopes interact with that resource.
“Using” assumes that resource is a local object but that’s rarely actually the case - as in the example of a DB connection - it’s just how OOP represents it.
A good counter-example would be if I need to manage a resource via an API. Maybe one request creates the resource and another tears it down. (Eg, begin a transaction and then commit it). “Using requires me to implement a new class to represent this resource - that’s OOP at its finest - whereas “defer” allows just doing the thing (and works equally well for objects)
Just because it's on objects doesn't mean it's OOP.
Can you elaborate on this more? It would be hard to represent a DB connection without an object somewhere (or a closure that's effectively the same thing), and I don't understand how local or not comes into play.
> Using requires me to implement a new class to represent this resource
It just requires you to set a key. You could also have a function to add it or do it locally.
Locally setting up Symbol.dispose does require more boilerplate than `defer`. But if the dispose is built into the object then `using` is less boilerplate than `defer`. And if you use a function to add the disposal, `using` with an extra function call is also less boilerplate than `defer`.
logger.log("start")
defer logger.log("exit")
...
for keeping alive a reference (because you need to call C code) global.keepalive = myvar
defer global.keepalive = undefined
...
// call C code here
etcIf you have a one-off, defer is nicer looking but using is perfectly capable of doing the job:
function defer_it(callback) {
return { [Symbol.dispose]: callback };
}
// will look better with using void
using _ = defer_it(()=>logger.log("exit"));
Or use DisposableStack() as another comment in this thread mentions. let connection = getConnection()
using stack = new DisposableStack()
stack.defer(() => connection.dispose())
The proposal even notes the `defer` method name was borrowed from golang.Though in practice I think the `adopt` method will be the nicer, more common pattern for transitional things before `[Symbol.dispose]` becomes more widely utilized:
using stack = new DisposableStack()
const connection = stack.adopt(getConnection(), c => c.dispose())Why does the caller have to figure out how to dispose?
Let the callee say how to do that.
let connection = getConnection()
defer connection.dispose() # dispose? close? finalize? quit?
vs await using connection = getConnection();
Same with C++ RAII. Connection connection();Rather than how the caller have to figure out when to dispose.
Defer\using will wait till we out of scope or till we return. While it may be much better to close\dispose as soon as possible or after some event.
Even you don't use the 'using' sugar. You may still be benefited from it if you need to implement some sort of resource management someday.
Instead of write a tear down function for every single type of resource in your code. Probably everyone will just agree upon that `Object[Symbol.dispose]()` will free this resource. And you only need to do it once and for all.
Connection.
...and it gives you an instance in a local variable with the lowercase initial to match the type.Like the "Churchill Martini" where he — according to legend — asked the bartender to pour the glass full of gin, and then he'd nod at the vermouth bottle on the shelf and say "Vermouth."
export function using<T extends { dispose(): void }>(
resource: T,
func: (resource: T) => void
) {
try {
func(resource);
} finally {
resource.dispose();
}
}
// usage
using(new Resource(), (resource) => {
// insert code that uses the resource
});We implemented a solution in bluebird at the time that used closures (it started with a simple sketch https://promise-nuggets.github.io/21-context-managers-transa... and ended up much more elaborate http://bluebirdjs.com/docs/api/promise.using.html) but this variant is so much cleaner without all the nesting and I'm glad its finally making its way into the standard.
All their supported proposals is about reflection, attributes (ie decorators) and in this case fine grained GC management (disposable resources).
I know a lot of their code embraces ES6 classes and such. I wonder if they will put forth a new proposal for dependency injection next, at this rate.
All this is to say, I don’t know how I feel about it
I believe that additional capabilities squeezed out of javascript if programmers can utilize type information at runtime.
DI hast no space in the Standard lib. It is Framework space. It belongs into react, svelte or Angular. But not into a standard lib.
Yes, there is a new-ish proposal [1] which is motivated in large part by DI.
(Full disclosure: I'm on the committee and don't really like DI or this proposal. But we'll see how it goes.)
[1] https://github.com/tc39/proposal-class-method-parameter-deco...
no surprise considering the TS people
Here's a snippet of C# I wrote recently to transform some JSON:
var transformed = media.Select(m => {
var parts = m.Split('/');
return new {
addedAtUtc = "2023-05-15T05:59:31.398Z",
originTripUid = parts[4],
path = $"{parts[4]}/{parts[5]}",
rank = "",
size = 103978,
stored = true,
type = "document",
};
})
File.WriteAllText(
Path.Combine(Environment.CurrentDirectory, "output.json"),
System.Text.Json.JsonSerializer.Serialize(transformed)
);
It would be easy to mistake this as JS at a quick glance.Duck Typing at runtime is possible and to a degree checkable and compile-time.
var transformed = media.Select(m => {
var parts = m.Split('/');
return new {
addedAtUtc = "2023-05-15T05:59:31.398Z",
originTripUid = parts[4],
path = $"{parts[4]}/{parts[5]}",
rank = "",
size = 103978,
stored = true,
type = "document",
};
}).ToList();
transformed.AddRange(otherMedia.Select(m => {
var parts = m.Split('/');
return new {
addedAtUtc = "2023-05-15T05:59:31.398Z",
originTripUid = parts[4],
path = $"{parts[4]}/{parts[5]}",
rank = "",
size = 103978,
stored = true,
type = "document",
documentType = parts[6]
};
}));
Adding "documentType" breaks in C# but would work in a structurally typed language as a structural type checker can see that the second result fulfills all of the necessary properties. Doing this in C# would require creating an interface and being explicit.Function dispatch: TS is purely dynamic; C# has both static and dynamic
OOP: Mandatory in C#, optional in TS
Variance: Implicit in TS, explicit in C#
Numeric types: TS has one (number); C# has the entire signed/unsigned/float x 8/16/32/64-bit matrix
There's really no intentional effort to converge them.
[0]: https://www.typescriptlang.org/docs/handbook/2/everyday-type...
JavaScript is a cool dynamically-typed language though. C# is on another side of the spectrum: it is super cool by being fully strongly-typed. But TypeScript is something in the middle and this uncertainty gives pains.
Not really.
For personal projects or if you're a freelancer sure. Once you work in a company/startup you can't go and implement things in whatever you like.
It's way easier to convince any manager that Dart/Flutter is a good choice for a mobile app because of the tech advantages than in anything else because of the pool of available developers.
Standard library that isn't worthless - no more npm package spam.
Type knowledge in the runtime. It doesn't have to be typed, just make it so I don't have to do crazy checks on properties before casting to a class.
Imports without webpack.
You do have 'instanceof' at runtime, so unless you mean some other type of 'class', casting to a class is really a non-issue (and you don't really 'cast'... since dynamic typing and all). If you mean some more advanced concept (e.g., structural type runtime checking), think how you would do it in statically typed languages (is it really better?).
function Foo(s) {
this.s = s
}
Foo.prototype.bar = function() {
console.log(this.s)
}
const baz = new Foo("Baz")
baz.bar()
But it seems Typescript can't handle it?(1) imperative style mutation of the prototype is runtime dependent, and typescript can't really rely on runtime behavior.
(2) Foo has two incompatible types in JS, it is both `(s) => void` and `new (s) => Foo`, typescript by default will assume the first signature (which is usually what people want).
In any case, you can specify the types (though in 99% of the cases, just use a class):
interface Foo {
s: string;
bar(): void;
}
const Foo = function(this: Foo, s: string) {
this.s = s;
} as {
new (s: string): Foo;
(this: Foo, s: string): void;
};
Foo.prototype.bar = function(this: Foo) {
console.log(this.s);
}
const baz = new Foo('Hello');
baz.bar()While that is a fair statement, I've been reading a lot of Typescript code lately and it seems in 99% of cases the selected solution is to use top level functions and globals to avoid the nesting hell class introduces, albeit introducing the global hell in the process. In practice, 'just use a class' doesn't overcome the human element.
Javascript was on the right track originally, but then veered off into the weeds for some reason. I appreciate you pointing out how it can be done. Although the solution is very much second class citizen, sadly. This certainly isn't going to wean developers away from global hell. Hopefully Javascript/Typescript can make some strides in improving this going forward.
We are talking about runtime class creation with dynamic methods, while still adding proper static types. There aren't many language allow this level of flexibility.
You can probably disallow prototype use with existing tools if that's desired, but it is an advanced tool for advanced use cases (which still exist).
Given:
class Foo {
constructor(x) {
this.x = x
}
bar() {
return this.x
}
}
maybe it becomes: object Foo(x) {
this.x = x
}
function Foo.bar() {
return this.x
}This is exactly what bugs me about TypeScript. The language itself is fine—some annoying things, but generally they’re due to having to run on top of JS. But Microsoft had the chance to fix JS’s terrible standard library. And they didn’t!
I know that's less of a concern nowadays with modern http, but guess who still supports ie11
It feels like terrible advice to give someone, given ESM is far superior to AMD, and this would be going backwards in time, but in your case you are already blighted by the disease and it is advice that might staunch some of the bleeding.
JS already has 'with', so 'using' seems like to closest semi-standard keyword to use.
But since TS is supposed to be a superset of JS, of course this does not change anything
const resource = {
[Symbol.dispose]: () => {
console.log("Hooray!");
},
};
Why not just const resource = {
dispose() {
console.log("Hooray!");
},
};
Why introduce this weird Symbol stuff?> Prior to well-known Symbols, JavaScript used normal properties to implement certain built-in operations. For example, the JSON.stringify function will attempt to call each object's toJSON() method, and the String function will call the object's toString() and valueOf() methods. However, as more operations are added to the language, designating each operation a "magic property" can break backward compatibility and make the language's behavior harder to reason with. Well-known Symbols allow the customizations to be "invisible" from normal code, which typically only read string properties.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Essentially if they use dispose() like you mention, what happens if someone already has code that has dispose? And in future, avoid people accidentally calling these.
Also, you are assuming the consumer of a class is same as someone that used / uses the class. What if the library author has dispose and someone wrongly use using with it?
The symbol `Symbol.dispose` is special, unlike any normal string or identifier, because it didn't exist until the new standard, so existing code can't have been using it. This guarantees a level of backwards compatibility that wouldn't exist otherwise.
Vs this won't break anything existing.
Because this potentially break every existing object with a `dispose` symbol.
That is exactly why symbols were added in ES6, and what they're used for. You're just 8 years later.
"Well-known" symbols include things like Symbol.iterator that make "for...of" loops possible, Symbol.asyncIterator for async for loops, Symbol.hasInstance for instanceOf, Symbol.toStringTag for toString, and others.
Typescript is strongly typed too so I assume it won't let you use using on things that don't support it.
If you are a big library author and you don't bother to, probably other will just write a wrapper(request-promise for example) or make a clone with that buildin(there are so many and it's pointless to count). It's FOSS anyway.
I'm sure there are examples of libraries that don't do this but I can't think of any off the top of my head and I'd assume that they're fairly rare.
function inner(defaultResource) {
const resource = defaultResource ?? getResource();
using resource;
}
That is, if a defaultResource is passed in, the caller should be responsible for disposing it, while if it wasn't passed in, the inner function should be responsible for disposing it.IMO cases like this are better handled with linting rules that forbid use of using on anything but an object directly returned by a function on that line.
The biggest problem I see is that to use the feature in a class you'd be forced to define a static factory method to produce the object-disposer tuple. To be effective, a static factory needs to be paired with a private constructor, but JS has no first-class support for those, which means you have to go to contortions with exceptions to make sure people don't accidentally instantiate a non-disposable instance of the class.
The existing proposal uses a well-establish way to interact with language features—the well-known symbol [0]. It's compatible with any way of defining an object, and doesn't require using any particular pattern that may be unnatural for a subset of the community.
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Some of the React hooks in Isograph use this pattern, and as a result the constructor returns an instance in an invalid state: https://github.com/isographlabs/isograph/blob/1a8182355ac9d1...
I'm of the opinion that this is a paper cut worth stomaching in favor of the addition of an idiom that provides genuine safety. The type for the class and the static conductor (ie a free function) could be exposed, but the class itself could be hidden.
I generally avoid new TS features (apart from typing that gets compiled away) until it looks like they are going to make their way into JavaScript, anyone know if thats being considered?
--
Edit: Yes it's being considered, looks likely, but not decided - https://github.com/tc39/proposal-explicit-resource-managemen...
Well, half of it at least :)
Python has both enter and exit, while this proposal only has the exit half.
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
The polyfill adds code at the start of the block to make an array, and code at the end of the block to call dispose on everything in the array. Then the transpiler just has to turn "using" into an append.
There's some other details to handle exceptions but that's the basics of how it works.
Edited: The code, polyfill or native, doesn't really know if things can be disposed. It just tries to do it, because you told it to. If the variable is null it skips it, otherwise it asserts that the dispose function exists at using time, and blindly runs it at the end of block.
What happens if you save a reference to (or return, etc.) the variable you declared with "using"? Then it will end up automatically disposed, but from the programmers point of view it should still be usable (not disposed).
Then that programmer had their hopes too high. The basic "using" keyword is not for variables you want to move between scopes. Javascript doesn't do reference counting.
For saving/returning, you need to use explicit instances of DisposableStack and .move()
So speaking specifically of a new TypeScript feature is justified here.
I think it's fair enough if you're coming from a TS perspective, writing TS (specifically) or blogging about it. Obviously there's overlap.
For a language like Typescript which borrows so much of its syntax from C/C++ it would be best to either make the same syntax mean the same thing, or instead make up new syntax. Call it "with" or "defer", or something else instead.
Also the syntax "Symbol.dispose" to denote a symbol seems overly verbose. Why not use a single prefix character like Lisp/Ruby ":symbol".
As for Symbol, it's a JavaScript feature. I bet it was introduced like this so symbols could be polyfilled, so adopted more easily, without making the lack of support a syntax error - i.e. no need to transpile this. A bit verbose, but bearable, and probably easier to parse for a human than a colon. Too much of such terse syntax rapidly makes the code harder to read and more difficult to look up for a novice, or possibly even for someone experimented..
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref... https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
And the choice was made by JavaScript. TypeScript is honoring that.
If you use react, it can get a lot more interesting[0]
It's a closer cousin of Python's context manager (aka C#'s using statement — probably where it actually comes from — and Java's try-with-resource), which probably hew closer to Common Lisp's unwind-protect (that is not type-oriented, but usually you'd create your own macro around that).
Why on earth does someone think it is a good idea to finish some work at some arbitrary time instead of what is expected when reading the code top down. Puzzling.
I expected that it would be part of a block initializer. e.g. `using x { /* scope where x exists */ } /* x was disposed when the block ended */`. This would also make it clearer that it can work even without assignment or with destructuring assignment (the outer object is disposed after the block even if it is not used).
I wonder why they decided not to do it like that.
It's not much different then let -- you're defining a block scoped variable that just happens to call the dispose symbol function when the block ends.
If you elaborate on what working with closures and not working with closures would look like, I could try to answer that.
using res = getResource();
const { x, y } = res; // ok
using { x, y } = getResource(); // error using getResource() as res; using someMutex.lock();
With the assignment syntax it's not obvious to me whether that's allowed.Much easier to not do it.
const using x = getX()
const {using prop1, prop2} = using getX()
void using getX()
// x, prop1 and 2 anonymous Xs to dispose
Instead of giving `using` the power / the burden of variable creation. This also solves `using mutex.lock()` mentioned elsewhere itt.To be fair I don't see it not being supported either by the examples.
{
await using { connection } = getConnection();
// Do stuff with connection
} // Automatically closed!
edit: according to the ES proposal issues, this might be something that doesn't actually exist, on purpose (it's rather ambiguous as to what will be disposed of): https://github.com/tc39/proposal-explicit-resource-managemen...I did also click through to the proposal but didn't see that issue. It does seem like it's still unclear what's intentional or not.
https://tc39.es/proposal-explicit-resource-management/#prod-...
What would that even mean? The binding in a `for-in` can only ever hold a string, which can't be disposable. With a `for` or `for-of` the binding can hold an object, which is something you could dispose of.
I found the syntax a bit confusing though. `await` comes before a promise, except in this case. I wonder what the reasoning behind the syntax was.
For what it’s worth, I always thought this syntax for await felt more natural:
let await user = getUser()
But now we have it both ways for different use cases and it’s weird.Keep in mind, await may need to be in both sides:
await using connection = await getConnection()
If it was `using await` that might give some confusion if it was awaiting the left or right side or the future disposal.Similarly, it gets fun combined with AsyncIterable:
for await (await using connection of getAllConnections()) { } with open(“file.txt”) as f:
f.write(…)So Sun microsystems is trying to EEE Microsoft since C# is mostly from Java?
I know the reasons that they don't do this, but I think without that kind of metaprogramming, typescript and node remain poor choices for backend development
class Foo {
foo: number | undefined;
bar: string | undefined;
};
console.log(Object.keys(Foo)); // prints []
console.log(Object.keys(new Foo)); // prints []For example, we wanted to create a Query DTO for our API. It would be a generic class that allowed you to specify the fields you wanted to return in a query, like:
{
include: ["Field", "Foo", "Bar"]
}
You can construct this in typescript like: class Foo {
bar: number | undefined;
foo: string | undefined;
};
class Query<T> {
includes: Array<keyof T> | undefined;
};
And use the query like: let q = new Query<Foo>;
q.includes = ["foo", "baz"]; // fail: "baz" isn't in `keyof Foo`;
But since types are erased at runtime, and typescript classes don't work like ES6 classes (which would actually make a hack to support this kind of runtime validation available), you can't validate through an enum on the field, like: class Query<T> {
@ApiProperty({
enum: keyof T // meaningless at runtime, won't compile
});
includes: Array<keyof T> | undefined;
}
And since Object.keys doesn't work, you can't validate the keys for the object at runtime unless you ditch the generic and just write out the same boilerplate code over and over again with different values for every type you want to be able to query.Typescript was obviously designed to run in a browser, where it originates data, and not on the backend where it receives data, since input validation is such a chore on the backend.
I don't even want a runtime call for `keyof T` like `typeof`; I just want typescript to compile `keyof T` into an expanded array of strings. I would even settle for a C/C++ style preprocessor macro support. There have been times when I wish I could just log the filename of a function in an error using a preprocessor like in C/C++:
cout << "Error in file: " << __FILE__ << endl;const resource = { [Symbol.dispose]: () => { console.log("Hooray!"); }, };
An object called resource, consisting on a anonymous function that logs Horray to the console. This is all fine and I am used to that.
The interesting part that stuck with me is "[Symbol.dispose]:". Why is it in a Array? What does this exactly do? It marks the anonymous function as disposable at the end of the code block? Correct?
{ [Symbol.dispose]: () => {...} }
const name = "key"
const example = { [name]: "value" };
This would result in
const example = { key: "value" };
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Symbol.dispose is not a string, but a symbol. Its value cannot be written out in the code, only referred to indirectly (that is basically what symbols are for), nor is there special syntax for properties with symbol keys like there is for properties with string keys, because that would be a nonsensical proposition due to the aforementioned fact. Thus, you cannot just write the property name directly, you have to use the computed property name syntax: [Symbol.dispose].
const dispose = Symbol.dispose
const resource = { [dispose]: () => {...} }Symbol.dispose is Symbol.dispose. It's a singleton object and that's the sole reference to it.
> Why did they make it so unwieldy especially since it is a new functionality not requiring backwards compatibility hacks?
1. Bare names in object keys are strings, so there needs to be an "escaping" syntax for non-string keys, that is what the brackets have done since 2015, in order to support the addition of...
2. Non-conflicting keys, which are what symbols are: symbols are guaranteed not to conflict with any string, and even to be unique unless they're created using `Symbol.for` (which accesses and stores into a global registry, this allows adding protocol methods to all objects without the risk of breaking existing any (by triggering new and unexpected behaviour).
await using { connection } = getConnection();
If you are used to Rust semantics, you'd expect that the whole thing is disposed, apart from still living objects. Reading this line I am immediately afraid that the whole object is disposed immediately (or right on the next line), the callback is called and your `connection` a couple of lines later will be closed already.
I continue to be disappointed that only Rust and C++ have automatic destruction. People already regularly forget to use Java's try with resources or Python's with, or Go's defer.
Isn't the point of computers that they can remember stuff for us? Everyone agrees automatic cleanup is the way to go for memory but not any other kind of resource?
With static typing you could opt into affine or linear types and those could get special cased by the compiler (to ensure a single reference is possible, and trigger the drop in case of affine types), but first you do need static typing (that's not javascript), and then you need to update the entire language to correctly handle the new affine or linear behaviour.
And that can lead to a lot of weird shit with languages not designed for that (especially linear typing).
with-objects binds variables to objects, like let. When it terminates, the GC finalizers of the objects (if they have any) are invoked.
1> (with-objects ((x [finalize (list 1 2 3)
(lambda (obj)
(format t "bye-bye ~s\n" obj))]))
(format t "inside with-objects: x is ~s\n" x))
inside with-objects: x is (1 2 3)
bye-bye (1 2 3)
Mainly this is meant to be used with OOP structs, where you can declare finalizers. 1> (defstruct widget ()
(:fini (me) (put-line `@me's finalizer`)))
#<struct-type widget>
2> (with-objects ((w (new widget)))
(put-line `have @w`))
have #S(widget)
#S(widget)'s finalizer
t
It provides a kind of RAII that can be useful from time to time.1. Is dispose run when the object is Garbage, or when the nearest scope exits? 2. Is `using` const or let? 3. Is dispose looked up on declaration or before call? 4. Are chained resources disposed, or only the last? 5. What happens if the using resource is not disposable?
2. Const.
3. On declaration.
4. I don't know what "chained resource" means. If you mean multiple declarations within the same scope, they all get disposed, in LIFO order (even if disposing one of them throws).
5. An error when the `using` statement runs. (Unless the RHS itself is null, rather than an object without a `Symbol.dispose` method; this lets you have conditional declarations.)
[0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
static String readFirstLineFromFile(String path) throws IOException {
try (FileReader fr = new FileReader(path);
BufferedReader br = new BufferedReader(fr)) {
return br.readLine();
}
}
Instead of defining a function with Symbol.dispose, you have to implement the java.io.Closeable interface.Java has had this since, cough cough 2014.
I get it but haven't really seen that sort of thing before.
Symbol.iterator comes to mind as a decent example.
Would somebody actually add "using" without checking type?
This link might work when TS 5.2.0 is released.
Seems the current design makes it very easy to forget adding the `using` keyword, leaving resources dangling.
Compare this to Go code, and I'll take the equivalent Go code any day of the week.
Presumably you would want to wrap that in a try/finally so that you don't accidentally not release the client if there is an exception.
You would go from:
const client = await pool.connect()
try {
// Do stuff. Possibly throw.
} finally {
client.release()
}
to await using client = pool.connect()
// Do stuff. Possibly throw.