Looking for RAII in Javascript

“Resource Acquisition is Initialization” truly is a delightful concept in C++. This lets you use resources without thinking of things such as closing a file (std::fstream), releasing a mutex (std::mutex) or releasing a pointer (std::unique_ptr). For instance, this is safe and idiomatic C++:

int main() {
  std::ofstream file("example.txt");
  file << "Hello World" << std::endl;
}

Safe, because when the end of the function is reached, C++ automatically destroys all objects of the scope. Even if an exception causes the stack to unwind. Destruction is deterministic. This is a powerful way to enforce a program’s correctness.

In C#, the garbage collector makes things a little different. But when it comes to resources, the Dispose Pattern comes to help. You can employ the using construct on IDisposable instances:

using (var file = new StreamWriter()) {
  file.WriteLine("Hello World");
}

Here again, this is safe and the file will be closed even in the case of an exception.

In Javascript, we’re out of luck. No such things as deterministic destruction of objects, nor disposables defined at a language level.

Files in Node.js

In Node.js, writing a file with the raw functions is error-prone:

function hello(cb) {
  fs.open('example.txt', 'w', (err, fd) => {
    if (err) return cb(err)
    fs.write(fd, 'Hello World', (err) => {
      fs.close(fd, (err2) => {
        cb(err || err2)
      )
    })
  })
}

Using streams makes the code much more legible:

function hello(cb) {
  var stream = fs.createWriteStream('example.txt')
  stream.write('Hello World')
  stream.close()
}

However, we still need to close the stream explicitly. That means if an exception is thrown in-between (possibly caught at the call site), the stream will leak; at least until the next garbage collection.

Closures Everywhere

There’s a neat solution to this in Javascript. It is common and cheap to make closures, so we can leverage this to control the lifetime of our resources:

function hello() {
  usingWriteStream('example.txt', (stream) => {
    stream.write('Hello World')
  }
}

Where usingWriteStream takes care of opening and closing the stream even in the case of the unexpected. An implementation would be as follows:

function usingWriteStream(name, handler) {
  var stream = fs.createWriteStream('example.txt')
  try {
    return handler(stream)
  } finally {
    stream.close()
  }
}

We create the stream, then call the handler. The finally clause ensures we correctly close the stream whatever happens.

It turns out many other libraries could return streams, so there’s an opportunity for genericity here. How about taking the ‘create’ function as argument?

function usingStream(createFn, handler) {
  var stream = createFn()
  try {
    return handler(stream)
  } finally {
    stream.close()
  }
}

We can then conveniently compose the function to build our initial usingWriteStream version:

var usingWriteStream = (name, handler) =>
  usingStream(fs.createWriteStream.bind(null, name), name)

Thanks to closures, we can build a RAII-like pattern in Javascript, ensuring our resources won’t leak. This is useful in a number of cases, even when the ‘resource’ is not immediately identifiable. For example, the function Map#withMutations(), in the Immutable library, uses a similar pattern:

var map1 = Immutable.Map();
var map2 = map1.withMutations(map => {
  map.set('a', 1).set('b', 2).set('c', 3);
});

In that case, the “mutable map” is the resource. This ensures this object cannot leak easily outside the current scope, and that we properly convert it back to an immutable map in the same frame. No explicit conversion is done.

Mind the Asynchronous

We can use a very similar pattern with asynchronous logic, by making the handler asynchronous. Our very first example with files could translate as such:

function hello(cb) {
  usingFile('example.txt', 'w', (fd, cb) => {
    fs.write(fd, 'Hello World', cb)
  }, cb)
}

Where usingFile takes care of closing the file even in case of error. Notice the last argument, that is the function called when the work is done and the resource is closed. The implementation of usingFile is left as exercice.

More interestingly, we can imagine the same pattern with Promises:

var usingFile = (name, handler) =>
  using(promiseOpenFile.bind(null, name), promiseCloseFile, handler)

function hello(cb) {
  usingFile(name, (fd) =>
    promiseWriteFile(fd, 'Hello World')
  ).then(() => cb(), cb)
}

For the purpose of the exercice, we assume the existence of promiseOpenFile and promiseCloseFile, returning Promises, respectively resolving to an opened file number, and to a closed file. Let’s have a try at the implementation of the using() function:

function using(open, close, handler) {
  return open().then((resource) =>
    handler(resource).then(
      (retval) =>
        close(resource).then(() => retval),
      (reason) =>
        close(resource).then(
          () => Promise.reject(reason),
          () => Promise.reject(reason)
        )
    )
  )
}

We start by opening the resource. If this is rejected, the process stops here. Otherwise, we move on into the handler. It can take any amount of time before this promise is accepted or rejected. In either case, we close the resource before forwarding the result. Notice that if closing the resource itself failed, we only forward the first error that happened. Another possible strategy would be to build an error object containing both reasons.

It still isn’t RAII

In C++ one can write code like:

class Hello {
public:
  Hello(): _file("example.txt") {}
  void write() { _file << Hello World << std::endl; }

private:
  std::ofstream _file;
}

This is still safe, because whenever Hello is deterministically destroyed, _file is destroyed as well. In Javascript, we cannot reproduce the pattern with callbacks like we did. But we could imagine the following:

class Hello extends Disposable({ _file: fs.createWriteStream }) {
  constructor(fileName) {
    super({ _file: [fileName] })
  }
  write() {
    steam.write('Hello World\n')
  }
}

Where Disposable is a higher-order function returning a class (that is, a function). It takes care of creating the resources, and closing them when the object’s own close() function is called:

function Disposable(ctors) {
  var DisposableImpl = function (opts) {
    for (var name in ctors) {
      this[name] = ctors[name].apply(void 0, opts[name])
    }
  }
  DisposableImpl.prototype.close = function () {
    for (var name in ctors) {
      this[name].close()
      this[name] = null
    }
  }
  return DisposableImpl
}

(A real version would need error handling, etc.) Because Disposable exposes its own close() function, this pattern is composable:

class SuperHello extends Disposable({
  _hello: (name) => new Hello(name),
}) {
  constructor() {
    super({ _hello: ["example.txt] })
  }
}

Although this is less elegant than the C++ RAII pattern, it provides an additional level of robustness. We could imagine more capabilities, like accepting custom ‘dispose’ functions instead of close(), etc.

That’s it! Use RAII-minded patterns and make code robust. Happy Javascript hacking!


Find me on Twitter, edit that article.