Disclaimer: This is just a proof of concept.
Intro: Getters and Setters
In JavaScript, you can bind an object’s property to a getter and/or setter function to be called when the property is accessed or set. This is best explained with an example:
var obj = {foo: 'bar'};
Object.defineProperty(obj, 'json', {
get: function () {
return JSON.stringify(this);
},
set: function (data) {
data = JSON.parse(data);
for (var i in data) {
this[i] = data[i];
}
}
});
//When accessing the property 'json', our getter function is called
console.log(obj.json);// => {"foo":"bar"}
//When setting the property, our setter function is called
obj.json = '{"hey":"cool"}';
console.log(obj.hey); // => "cool"
Getters and setters are strictly synchronous, but what if we could enable an asynchronous version of each?
Asynchronous getters
Many Node.JS frameworks rely on the concept of middleware. For example, when retrieving session data, Express/Connect passes each request through a chain of user-specified middleware, one of which might retrieve the session data. What if only some of our requests need the session data? Conditional middleware is ugly. Wouldn’t it be nice if we could just define this:
Object.defineProperty(request, 'session', {
get: function (callback) {
//<something async to retrieve session data>
callback(null, {foo: 'bar'}); //(err, session)
}
});
My node.js synchronous library uses node-fibers and some trickery to enable asynchronous getters and setters. One use for the synchronous library is to convert an asynchronous callback to an asynchronous getter.
Asynchronous getters mostly negate the need for middleware as resources don’t need to be parsed or retrieved until they’re needed. For example, we could define an asynchronous getter to retrieve session data, another to parse request parameters, etc.
How do I use it?
npm install -g synchronous
Say we have a method which parses a request body and extracts parameters:
var http = require('http')
, query = require('querystring')
, req = http.IncomingRequest.prototype;
req.params = function (callback) {
var data = '';
this.on('data', function (chunk) { data += chunk; });
this.on('end', function () {
try {
callback(null, query.parse(data));
} catch (e) {
callback(e, null);
}
});
}
You’d typically use the method like this:
req.params(function (err, params) {
console.log(params);
});
With the synchronous library, we can convert the method to a getter using the syncGetter method. When run in the same context, these two examples will output the exact same thing
require('synchronous');
req.syncGetter('params');
console.log(req.params); //No callbacks!
The syncGetter method is available to all objects and has the added benefit of caching the result so that the async function is only called once. There’s also the syncMethod and syncSetter methods available.
Doesn’t this block and defeat the purpose of Node.JS?
No. The asynchronous getters block the currently running fiber, not the entire event loop. Node is still non-blocking and fast.
So what’s the catch?
- This is non-standard Node.JS. To use async getters, you need to run your app with `node-fibers` rather than `node`
- The resulting synchronous code needs to be run in its own fiber. For convenience, I’ve proxied the `{http,https,net}.createServer` methods so that each request has it’s own fiber.
- Converting async => sync code is ok if the callbacks were strictly nested (i.e. executed sequentially), but you lose the ability to use other async patterns (parallel, etc.).
- Fibers are generally opposed by the node community.
Further reading