Last Updated: September 09, 2019
·
13.82K
· irakli

"Callback Hell" in JS/Node.js is a Myth

If you befriend Async.js (https://github.com/caolan/async) you can avoid most "callback hell" scenarios in a Node code. Async,js can be a little heavy for the client-side, but Underscore.js (http://underscorejs.org/) also has some interesting methods, e.g.: after() method http://documentcloud.github.com/underscore/#after

13 Responses
Add your response

I'm also looking at http://maxtaco.github.com/coffee-script/ to help clean out some of the extra cognitive load.

over 1 year ago ·

Hint: If there are already two libraries to avoid callbacks it could be a sign that there are in fact too may callbacks. So I'd claim it's not a myth, it's not something that can be worked around.

over 1 year ago ·

I'd recommend something like Q because it's compatible with ES6 generators. That means that once the next version of JS comes out you'll be able to write code like:

spawn(function* () {
  var result = yield webRequestAsync('http://foo.com');
  //Do something with result
  console.log(JSON.parse(result).name);
});

which looks just like the sync equivalent, except for the yield keyword. It would then be exactly equivalent to:

function () {
  return webRequestAsync('http://foo.com')
    .then(function (result) {
      //Do something with result
      console.log(JSON.parse(result).name);
    });
}();

Consider that yield also works perfectly inside try catch statements etc. and you're onto a winner.

over 1 year ago ·

I've used Flow-JS before with good effect. It appears to be a bit lighter than the above. My personal use case was to handle the asynchronous hell that is the HTML5 File API, and it performed well.
https://github.com/willconant/flow-js

over 1 year ago ·

Wow, that looks almost exactly like Step, just with different nomenclature.
https://github.com/creationix/step

over 1 year ago ·

@Licenser They don't claim to AVOID callbacks, they abstract them. Callbacks are the whole point of asynchronous programming. These libraries are just trying to mitigate the "hell" part.

And it's worth mentioning that about 90% of the examples I hear of Callback Hell are really just examples of poor coding practices, like nesting 5 levels of anonymous functions.

Async programming is only ugly when it's done by people who don't understand it.

over 1 year ago ·

@Licenser all comments seem to indicate what you said, it's not a myth, actually it's not just 2 libs, there are many more libs out there to avoid it, and they follow different approaches.
Something I'd say is that if you find yourself in callback hell to often, you might be trying to write a lot in 1 function.

over 1 year ago ·

Don't you ever thought about using bind to "hold over" a function call and make your code look nice w/o any libs. Except using an internal Function.bind() (not in all browsers), you may write a 5-liner function like this one, the one which will "defer" calls to the function it wraps:

function def(f) {
    return function() {
        return (function(f, args) {
            return function() { return f.apply(null, args); };
        })(f, arguments);
    };
}

It will allow you to write something like this:

function logThis(arg) {
   console.log(arg);
}
logThis = def(logThis);

function queue() {
   var fs = arguments;
   for (var i = 0, il = fs.length; i < il; i++) {
      fs[i]();
   }
}

queue(logThis('one'), logThis('two'), logThis('three'));
> one
> two
> threee

It is just a draft to give a closer look to idea, different implementations will require different ways to call/implement it (i.e. partial functions, where you pass only some part of parameters in call and joining them with the ones left while doing a second call, like in Scala; btw, Function.bind() allows this, again), but I really doubt that proper variants of these implementations will take the same amount of KBs from user as most of those libs do — most of them are few-liners. Be monadic ;).

over 1 year ago ·

@shamansir can you explain how this is monadic instead of just composition (maybe its the same and I'm too dense to see it)? Trying to grok monads and maybe that would help me. Thanks!

over 1 year ago ·

I just wrote a post that details other approaches to avoid callback hell with sample code. http://adamghill.com/2012/12/02/callbacks-considered-a-smell/

over 1 year ago ·

I like @kriskowal's description of the situation "where code marches to the right faster than it moves forward"!

If you don't want the overhead of adding/ implementing a promise library then named functions can be a simple way to escape from callback hell.

over 1 year ago ·

@kevincennis no, callbacks are not the "whole point of asynchronous programming", they're just an implementation detail — and a terrible one at that! They are used in JavaScript for a couple of reasons:

1) It's extremely trivial to do manual continuation-passing style in a language with first-class functions. You just replace return foo with f(foo).

2) The language doesn't come up bundled with anything else out of the box, so people just go with the approach that has least initial friction to get things working.

There are lots of problems with this approach, however:

1) Callbacks DO NOT compose. They create tightly coupled, call-site specific code, which in turn leads to maintainance hell — this is the actual problem, "callback hell" is, in fact, a myth. You can think about callbacks as a fancy word for "goto-based programming". Yeah, that doesn't sound good, does it?

2) In the long run, manual-continuation passing style will lead to lots of duplicated code, and code that are can't be easily abstracted over, this is a side-effect of Problem 1.

3) Callbacks do not integrate with all the other constructs in the language. They feel like little alien things, where you're always forced to know whether you're dealing with eventual values or immediate values. IOW, you code as close to the bare metal as you could (just like "goto").

There are other, better approaches for asynchronous programs, that allow you to actually compose things, in turn decoupling your code and making it more maintainable. Promises/A+ implementations are one way to do that. Other languages have things like Monads, Delimited Continuations and Promises/Futures.

I wrote a follow-up for this comment here: https://coderwall.com/p/ehzcuq

over 1 year ago ·

@Spaceghost's link above leads to IcedCoffeeScript, which I find to be the best way to avoid the "callback hell".

For non-purists / CoffeeScript haters - I wrote a long protip covering a real-world usage scenario here.

over 1 year ago ·