Resource management

# Isiah Meadows (7 years ago)

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

# Raul-Sebastian Mihăilă (7 years ago)

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);
});
# Jordan Harband (7 years ago)

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
}
# Raul-Sebastian Mihăilă (7 years ago)

I agree, but note that a resolved promise is not the same as a fulfilled promise (tc39.github.io/ecma262/#sec-promise-objects).

# Isiah Meadows (7 years ago)

But keep in mind it still doesn't cover two key issues:

  1. Synchronous resources do in fact exist (primarily in Node). You need both for it to be effective.
  2. 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.
# Jamesernator (7 years ago)

The for loop approach works for synchronous resources as well actually, there's nothing special about those awaited 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__).

# J Decker (7 years ago)

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?

# Isiah Meadows (7 years ago)

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.

# Isiah Meadows (7 years ago)

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

# Ron Buckton (7 years ago)

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

# Benjamin Gruenbaum (7 years ago)

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.

# Benjamin Gruenbaum (7 years ago)

And, on a similar note - a pattern has emerged in userland libraries all over: stackoverflow.com/questions/28915677/what

# Isiah Meadows (7 years ago)

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

# Benjamin Gruenbaum (7 years ago)

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.

# Isiah Meadows (7 years ago)

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.

# Benjamin Gruenbaum (7 years ago)

Promise.using solves all that - give it a spin :)