Search

Pony Foo

Ramblings of a degenerate coder

Taming Asynchronous JavaScript

(2 comments)reading time: , published

Last month, a series of very interesting articles regarding async coding style, in Node, popped up. The discussion spanned a few more subjects than just coding style, it was analyzed in a more theoretical level, as well as a deep technical level, obviously on the practical level, and even politics saw the limelight in this fascinating argument.

I'll try my best to put together a coherent article on the subject that doesn't put you to sleep. But I wouldn't bet my horse on that.

I'd bet yours, though.

I do realize I got one month too late to the party, but like I mentioned, I was too busy visiting coffee shops in Amsterdam. Without further ado...

Case Study: Callbacks vs. Promises

Firstly, I would like to cite the series of resources that prompted this blog post.

These are possibly the lengthiest articles written on the subject, but they definitely are the most interesting ones. I recommend to take your time and read through all of them, you won't regret it.

[...]

So, you've read them all? Great!

Lets back up a little bit, now, and point out the source of the conflict. Node supported promises early on, but they decided to drop support for them shortly afterwards, partially because of the volume of disagreement they generated. Thus, they sticked with callbacks.

call-me.jpg

The different modules packaged through npm, swiftly followed the convention of passing function(err, results){} as the last parameter, a convention was silently born.

My Point of View

While developing this blogging engine, and particularly the asset manager behind it, I made extensive use of async, which vastly improves upon the raw power of manually chaining callbacks.

I felt comfortable dealing with callbacks, but some more complex scenarios demanded a more robust solution, such as the ones async provides.

As far as promises go, I've been working with jQuery.Deferred for a while now, mostly in AJAX scenarios, though.

Recently, I started working on an application that makes more extensive use of promises, both through AngularJS on the client-side, and using Q on the server-side.

On the client-side, this makes perfect sense. We have jQuery performing similar operations, and the complexity requirements for asynchronous code aren't as steep as they are in the back-end.

Front-end async operations tend to be way simpler than those performed on the server-side. There rarely is the need to create a deeply nested hierarchy of callbacks: an event handler making an AJAX request and maybe even doing something else with the response. You might even have a thin layer that creates a cache of the responses, but that's about it. From this standpoint, it makes sense using promises.

On the other side, promises tend to result in more convoluted, complex code in Node.

broken-promises.jpg

To me, it's not a matter of simplicity, as James puts it, but rather, a matter of both practicality and productivity.

Practicality, because it's just more straightforward to use callbacks in an environment where it's the standard to do so. Productivity is a side-effect bonus here, bending the application to use promises in a portion of the code would be an unwarranted waste of time.

And think of the drawbacks. It would also be unnatural and lead to a chaotic codebase where everyone has an opinion on how code should look like. It's not even that promises are wrong, it's just that, at this point in time, they don't belong in Node.

Again, not because promises are wrong, or "broken", but they don't fit in, they don't accomplish anything that can't be done using callbacks. Besides, mixing both styles might even complicate matters, making it particularly confusing to handle errors, for example.

Error Handling Conventions in JavaScript

There is a very simple convention in JavaScript you should abide by, if you want to build successful and clean applications. And that is, how to write properly error-handling functions.

Synchronous code should throw, Asynchronous code shouldn't. Ever.

function sync(){
    try{
        return taskThatMayThrow();
    }catch(e){
        console.log(e); // handle
    }
}

function async(done){
    operation(function(err, result){
        if(err){
            return done(err); // bubble up
        }
        result += 1; // [...] further processing
        done(null, result);
    });
}

Simple enough, when dealing with asynchronous code, you should always bubble exceptions up, so that the caller can choose to either handle them, or bubble them further up the callback chain. This way, your application can handle errors gracefully, rather than quit unexpectedly (or require process.on('uncaughtException') in order to survive).

Conclusion

I definitely think Node is better off without promises, broken or otherwise. The client-side ecosystem is a little less well-defined, and as such, a more suitable home for promises, but even then, it's just a matter of preference to use one or the other.

Comments(2)

Jean Hugues Robert

Hi,

You may find interesting a solution I wrote that reduces the tension between callbacks and promises.

With it you can write:

var read = Parole();
fs.readFile( f, "utf8", read );
read.then( ok, ko );

See https://github.com/JeanHuguesRobert/l8/wiki/AboutParoles

Nicolas Bevacqua

This is an interesting alternative to promises in Node.

However, I just don't see the benefits in making an effort to turn away from the callback-based ecosystem. There's nothing wrong with it. And turning to promises (or even a solution like yours), only adds to the confusion.

I prefer simplicity, such as what you get from using async.

Thanks for your input.

Pony
Foo
Pony
Foo
Pony
Foo