Earlier today I was reading Node: Up and Running. The book itself was pretty good–I enjoyed reading it–but one example illustrates something that has been bothering me lately.
Below is a piece of JavaScript code that creates a simple web site:
var express = require("express");
var app = express.createServer();
app.listen(8000);
var tweets = [];
app.get("/", function (req, res) {
res.send("Welcome to Node Twitter");
});
These five lines of code require readers to be aware of JavaScript's event loop and the fact that implementation of app.listen is asynchronous. Otherwise one will end up wondering why there is no race condition between app.listen and app.get calls.
Now imagine for a second that we live in a parallel universe where JavaScript has a defer keyword similar to one in Go. Let's rewrite our example using it:
var express = require("express");
var app = express.createServer();
defer app.listen(8000);
var tweets = [];
app.get("/", function (req, res) {
res.send("Welcome to Node Twitter");
});
Boom! Just like that we made this tiny example easier to read and comprehend without thinking about event loops and implementation details of listen.
Now don't get me wrong, I'm not arguing that we don't need to know about JavaScript's event loop u2014 because we do. But this reliance on implicit behavior is very popular among JavaScript developers. You can find similar examples everywhere: from programs that rely on particular language oddities to modules that implicitly rely on other modules and their implementations.
Unfortunately, I don't have an elegant solution to this. I wish I had but I don't. I just thought that this small example nicely illustrates a bigger problem.
As you probably know we don't live in a parallel universe and there is no defer keyword in JavaScript. In fact, it is impossible to replicate the exact same syntax simply because its introduction will break the web: all programs and sites that use defer as a variable name will stop working. No browser vendor will ever go for it.
That said, Underscore provides a _.defer function which is basically a wrapper around setTimeout(fn, 0). Of course it is uglier (especially if you need to use this) but it does its job:
_.defer(function () { app.listen(8000); });
_.defer(_.bind(function () { this.listen(8000); }, this));
And with ES6 fat arrows it is actually not that horrible looking:
_.defer(() => { app.listen(8000); });
_.defer(() => { this.listen(8000); });
So, even though it'd be nice to have defer in JavaScript, you can't always get what you want.