I’ve been starting to play around with Node.js recently and I’ve come across a situation where I need a little guidance around the prescriptive node.js way of accomplishing a task. In this particular case I need to create a bunch of directories and, when all of the directories have been created I need to perform some final operation. The order in which the directories are created does not matter, I just need to perform a final operation after the last one is.
The easiest way the accomplish this would be to fall back to the old synchronous habits. That is, just call fs.mkdirSync for each of the directories and perform the operation at the end. For example:
fs.mkdirSync('a', 0755);
fs.mkdirSync('a/b', 0755);
fs.mkdirSync('a/b/c', 0755);
performFinalOperation();
While this would work, it doesn’t feel like it’s the node.js way of doing it. Obviously, the program would block while it waits for the OS to create the directory and return. On a heavily loaded system with a file system that’s mounted remotely each of the mkdirSync calls could take a very long time. So clearly, this isn’t the best approach.
One of Node.js main selling points is the fact that it’s asynchronous. So the calls to fs.mkdir could be chained through the callbacks:
fs.mkdir('a', 0755, function(e) {
if (!e) {
fs.mkdir('a/b', 0755, function(e) {
if (!e) {
fs.mkdir('a/b/c', 0755, function(e) {
if (!e) {
performFinalOperation();
}
});
}
});
}
});
Again, this approach I’m sure works, but it leads to really deep nesting and code duplication. It does have the benefit of not blocking while the directories are created, but at what cost?
Another approach would be to get really fancy in an effort to avoid the code duplication and nesting:
(function (directories) {
if (directories.length === 0) {
performFinalOperation();
} else {
var tail = arguments.callee;
fs.mkdir(directories.shift(), 0755, function(e) {
tail(directories);
});
}
})(['a', 'a/b', 'a/b/c']);
This approach makes use of all sorts of crazy stuff: anonymous self calling functions and the magical arguments.callee. But worst of all, it isn’t obvious what the code is doing at first glance.
So, while the concrete question is around creating directories I’m more interested in the approach that a seasoned node.js veteran would take when this sort of situation arises. I’m specifically not interested in what libraries are around to make this easier.
Oh, hey Bryan 🙂
The seasoned Node veteran has written at least one of their own control flow libs. We just copied Twisted’s
Deferredclasses since they already did the hard work and research into async programming. This inverts the standard callback-as-argument pattern and I like the resulting code, but if you want to nest a bunch of callbacks you can still do that withDeferredand end up with just as much of a mess.With the restriction of not using libraries people generally do exactly what you wrote. There really isn’t any other choice. Without language changes such as generators the best we can do is use libraries. If you don’t want to use an existing one you will end up rolling your own or just writing a lot of boilerplate.