Resource management
Such a protocol would make sense only if new specific syntax was added to the language. But is that really necessary when this can be implemented very easily without new syntax?
async function open(file, opts, func) {
const fd = await fsp.open(file, opts);
await func(fd);
await fsp.close(fd);
}
await open("/path/to/file", {mode: "r+"}, async function (fd) {
const bit = await fsp.read(fd);
});
You'd need to wrap the body of your open
function in a try/finally, and
do the fsp.close
in the finally
block - but otherwise that would
certainly work, provided that the promise returned from func
did actually
settle (resolve or reject).
Assuming fsp.open()
will always settle, but not assuming that func()
will, you'd need something more like this:
async function open(file, opts, func) {
const fd = await fsp.open(file, opts);
const funcPromise = func(fd);
return Promise.race([
delay(30), // implementation of a function that returns a resolved
promise in 30s left to the user
funcPromise,
]).finally(fd => fsp.close(fd)); // assuming the "finally" proposal is
available
}
I agree, but note that a resolved promise is not the same as a fulfilled promise (tc39.github.io/ecma262/#sec-promise-objects).
But keep in mind it still doesn't cover two key issues:
- Synchronous resources do in fact exist (primarily in Node). You need both for it to be effective.
- Your suggestion isn't composable at all (like nearly every other callback-driven API), and it prevents returning from inside the block without the use of exceptions.
The for
loop approach works for synchronous resources as well
actually, there's nothing special about those await
ed things e.g.
const fs = require('fs')
function* open(file, opts) {
const fd = fs.openSync(file, opts)
try { yield fd } finally { fs.closeSync(fd) }
}
for (const fd of open("/path/to/file", {mode: "r+"})) {
const bit = fs.readSync(fd)
...
}
I can definitely imagine a Python-esque with
statement though (perhaps
with Symbol.open/Symbol.close
) then you just use something like
Python's contextlib
(docs.python.org/3.7/library/contextlib.html) utility functions
for converting generator based resources to Symbol.open/Symbol.close
.
For now though the for
loop approach is a relatively good workaround
(as next/return
effectively emulate Python's __enter__/__exit__
).
Just a shot; but something ilke deasync ? www.npmjs.com/package/deasync
it's not so much about ordering wait in the current code, but the current code within outer code that's the issue somehow?
Certainly not optimal, though, and I'd rather it not become the
primary idiom - it lacks the intent of a Python-like with
statement.
Also, next
/return
doesn't exactly mirror __enter__
/__exit__
from Python. What really happens with for ... of
is closer to
next
/next
, where the first call reports {done: false, value: whatever}
, and the second {done: true}
. It's more of a unusual pun
than anything, a theoretical equivalence providing an unintuitive
workaround.
I would welcome a strict-mode-only modification to with
that allows
something like that, and adding with await
for Promise-resolving
equivalents.
Not enough. Not looking for an await
replacement, but something
closer to Python's with
statement or Java's try
-with-resources.
Or, if you want something
Python: preshing.com/20110920/the-python-with-statement-by-example
Java: docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
JavaScript's idioms tend towards Python, Java, C#, etc. in how
resources are handled, so things like Ruby's begin
-ensure
wouldn't
work as well (that was where I got my generator workaround from,
actually, and as you can see, it's not exactly very obvious).
Speaking of C#, the C# variant of this is the using
statement: msdn.microsoft.com/en-us/library/yh598w02.aspx
I'm not advocating using
as a new keyword for ES, given it's not already reserved and would have ASI hazards. The Java "try-with-resources" seems to be a fairly flexible syntax, though its adoption in ES would be predicated on some well-defined heuristic for determining which method to be used, given that ES does not have interfaces (and thus no System.IDisposable or java.lang.AutoCloseable). That would likely be an existing method name, such as "return" as used in iterators today, or a built-in Symbol which could be retrofit onto %IteratorPrototype%.
We've actually worked on this extensively in bluebird with
bluebirdjs.com/docs/api/promise.using.html and disposers which give
something similar to the with
syntax you describe.
There has also been work on a defer
like abstraction. The tricky parts
were mostly getting it to work with multiple resources at once.
I've been using it for a while now (few years) and I'm generally satisfied.
And, on a similar note - a pattern has emerged in userland libraries all over: stackoverflow.com/questions/28915677/what
It'd be nice to have something like that reified into the language
somehow. I'll note that Bluebird's gotcha with Promise.using
multiple resources simultaneously is a huge issue, though, and it
should be avoided for anything standardized. (If one of them fail, the
rest should be closed either immediately or as soon as they are
available, to avoid resource leaks.)
Oh, Bluebird's Promise.using
does that with very high certainly. The
reason we introduced using
rather than let people just use the disposer
pattern is because it is very tricky to get right in userland - so the
library provided it.
I'll note that the disposer pattern is a lot like Ruby's resource management idioms as well (probably closer). The biggest issue I've seen with it so far is only how hard it is to compose while still avoiding leaks. Using one resource like that is easy, but joining multiple resources to use them simultaneously is much harder, and really requires some sort of abstraction. Promises complicate that further in that you have to effectively save an async continuation for each resource just to call back into them when done. Another complication is that some may end up unsuccessful, so you have to continue those that do succeed.
As for any decent resource management, the current idioms suck, but I'm not sure what would be better.
Promise.using solves all that - give it a spin :)
In this GH issue in the async iteration proposal, I found that the async iteration concept itself could theoretically be used for resource management, such as in this example (copy/pasted from my initial issue):
const fsp = require("fs-promise") async function *open(file, opts) { const fd = await fsp.open(file, opts) try { yield fd } finally { await fsp.close(fd) } } for await (const fd of open("/path/to/file", {mode: "r+"})) { const bit = await fsp.read(fd) // do other things... } // descriptor automatically closed, so no resource leaks!
It's neat and all, but we really should have something much better than that. Maybe something like this?
// Strict mode only with (const fd = await open("/path/to/file", {mode: "r+"})) { const bit = await fsp.read(fd) // do other things... }
(I'm not totally sure about what methods the closeable/etc. API or protocol should use.)