ChaniBlog











{September 15, 2013}   From Desktop to Web, part 1: Callbacks for dummies

I’ve been meaning to blog about this for ages, but I seem to have writers’ block. Time to break through it!

Going from desktop kde/qt programming to mobile and web development has been quite a change. Some of the lessons I’ve learned along the way, I wish I could have read about instead of walking into. So, I’m writing about them now to save the next person from that. :)

Let’s start with node.js. I love node.js, it’s a lot of fun to work with. A shame there’s always web browsers at the other end. ;P But, node is a place where you are guaranteed to run into one of javascript’s biggest annoyances:

Callback hell

Now, for KDE programmers, callbacks might not sound so bad. Signals and slots are technically callbacks, right? And they’re easy and fun to work with. But when you strip away all that nice abstraction, you get a lot of ugliness. Ugliness like this:

doAsyncThingOne(data, function(error, result) {
    if (error) {
        ohshit();
    } else {
        doAsyncThingTwo(data, function(error, result) {
        if (error) {
            ohshit();
        } else {
            doAsyncThingThree(data, function(error, result) {
                if (error) {
                    ohshit();

…and so on. (sorry about the ugliness of the code formatting, that’s what you get on a free wordpress account.)

And that’s assuming that all your async methods use the “data, callback” pattern. it’s a popular one, but there’s another popular pattern, “data, successCallback, errorCallback” – and sometimes “data, {success:callback, error:callback}”. And then there’s the question of what pattern they expect for those callbacks you’re passing in – is it “error, results”? “results, error”? Something Completely Different?

It can get to be quite a headache quite quickly when the libraries you’re working with don’t all use the same pattern, and you always have to remember which one expects which. and there’s no compiler to warn you, either – you just get things breaking horribly or, just as often, silently dropping bits on the floor.

Cleaning it up

Fortunately the majority of libraries I’ve worked with follow the pattern in my example – including async, a library purely for reducing the pain of callback hell. Async can get that awful example down to something more manageable:

async.waterfall([
    function thingOne(next) {
        doAsyncThingOne(data, next);
    },
    doAsyncThingTwo,
    function thingThree(dataFromTwo, next) {
        doAsyncThingThree(dataFromTwo, function (error, result) {
            if (result.isCool()) {
                next(null, result);
            } else {
                next("not cool!");
            }
        });
    },
], function finalize(error, dataFromThree) {
    if (error) {
        ohshit();
    } else {
        yay(dataFromThree);
    }
});

note that since thingTwo fit async's callback expectations perfectly, we didn't even have to wrap it. :)

However, this is still pretty inflexible – what if a particular result from thingOne means that you can skip things 2 and 3 but still need 5? I've heard that Promises are more flexible in this regard, and I plan to try them out sooner or later, but I'm not sure what to do about all the libraries that don't offer a promises api. We shall see.

The ‘dummies’ part

Now, I actually got way off topic there. There’s a different problem with callbacks that inspired this blog post. I felt really goddamn stupid when I noticed I’d done it, but then I realised… everyone else working on the project had also done it somewhere.

See… the thing about callbacks, is that when you take one, you're promising to call it once, and exactly once. Not twice, not zero (excepting that thou then proceed to one ;). What we had was some callbacks that might never be called, and some that were always called twice. Or more.

Oops.

So, yeah, don't do that. It’s bad.

And… that’s it, really. I keep feeling like I should have more to say about it (One shall be the number of the counting, and the number of the counting shall be one?) but it’s just a matter of checking your code. And hopefully keeping the methods simple enough that checking the callback call doesn’t require a state diagram. :)

If you want more information, just google “callback hell”. There are plenty of other blogs about it. :) (ooh, Elm sounds interesting…)



unix0 says:

Going one or two steps further would be a maybe monad in js. The concern, however, would be most js scripters (scripties?) looking at it might be puzzled/confused more than helped.



Chani says:

ooh, monads. :) I never did get around to learning those properly. but yeah, that kind of stuff is brain-bending, and I wouldn’t want that in a shared codebase.



VitaminC says:

Compared to a clean actor library (like Scala/Akka, which has static type checking, Future/Promises, multithreading (AFAIK, node.js is single-threaded)) the complexity shown here really scares me. If it wasn’t for Javascript’s (force fed) ubiquity, I wouldnt be able to understand node.js’s success.



Comments are closed.

et cetera
Follow

Get every new post delivered to your Inbox.

Join 360 other followers

%d bloggers like this: