2

What's the best way to write a series of deeply nested callbacks?

You have several functions, each of which performs an asynchronous operation and which depends on the prior function's output. For two functions, this typically looks like foo(bar).

A long series of callbacks will look like foo(bar(baz(qux(...)))). This quickly becomes very difficult to read, let alone maintain. Especially once you begin passing additional parameters to the function calls! Step improves on this a bit, but adds an extra parameter to each function call.

This post (section 3.1) suggests a wait function, which is used like this: wait(foo, qux) and supports multiple dependencies like so: wait([foo, bar, baz], qux). Unfortunately, this won't help for nested callbacks.

Step and wait both help a bit, but neither seems ideal. Is there a better way to write long callback chains?

knite
  • 6,033
  • 6
  • 38
  • 54
  • 2
    Maybe you want something similar to jQuery's [`.queue()`](http://api.jquery.com/queue/). Each function in the queue gets a reference to the next function to which it could pass the arguments. – Felix Kling Sep 25 '11 at 07:44
  • Look at my answer here: http://stackoverflow.com/a/16160229/571230 I hope it somehow helps. – Karol Apr 23 '13 at 03:26
  • If you are after readability you should forget about chained code. Do one thing per line! – Semra Dec 30 '14 at 18:19

3 Answers3

3

In functional programming there is function call compose which create new function composition from two function f o g = f(g(x))

function compose(f, g) {
   return function() {
      //apply is use to pass multiple arguments to function g
      return f(g.apply(null, Array.prototype.slice.call(arguments, 0)));
   };
}

and if you have reduce function also from functional programming paradigm.

// this function need at least 2 arguments and it don't check if you pass 
// one element array or empty array.
function reduce(fun, array) {
    var result = fun(array[0], array[1]);
    for (var i=2; i<array.length; ++i) { 
        result = fun(result, array[i]);
    }
    return result;
}

you can create chain function using the two above

function chain() {
    return reduce(compose, arguments);
}

and you can use chain to create new function which is a chain of functions

var quux = chain(foo, bar, baz);//quux is function that call foo(bar(baz(...)));

you can also pass this to the other function as argument as a callback

some_function(chain(foo, bar, baz, quux));

in this case some_function get callback function as argument.

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • A good idea in general, but I think your approach is not 100% correct. I think the OP wants callbacks. In your case, `g` would be called and then return value would be passed to `f`, where actually `g` should be passed as callback to `f`. But I could be wrong... – Felix Kling Sep 25 '11 at 09:08
  • In that case is even simpler you can use `function compose_callback(f,g) { return function() { f(g); }; }` – jcubic Sep 25 '11 at 09:15
2

While inline functions are neat and can be clearer, sometimes explicitly defining a method, say within an object literal, and then making reference to those methods is more maintainable and reusable.

This is a trivial example, but is generally how I tend to structure my callbacks.


container = {};
container.method = function(arg) {
    //do your thing
    return container.method2(arg) ;
};
container.method2 = function(arg) {
    return function() {
        // work with arg, etc
    };
};
$('#element').click(container.method);


dwerner
  • 6,462
  • 4
  • 30
  • 44
0

Consider using something like this...

//Prepare a command graph
var fn = {};
fn["getAccount"] = function(){$.ajax(url, fn["getAccount.success"], fn["getAccount.failure"]);};
fn["getAccount.success"] = function(data){fn["getTransaction"]();};
fn["getAccount.failure"] = function(){fn["displayError"]();};
fn["getTransaction"] = function(){$.ajax(url, fn["getTransaction.success"], fn["getTransaction.failure"]);};
fn["getTransaction.success"] = function(data){fn["displayData"];};
fn["getTransaction.failure"] = function(){fn["displayError"]();};

//Initiate the command graph
fn["getAccount"](data);

I don't know why, but I really dislike underscores in variable and function names.

Jonathon
  • 155
  • 1
  • 11