views:

554

answers:

6

I have to interact with a remote api that forces me to chain requests. Thats a callback-hell in asynchronous mode:

// pseudocode: ajax(request_object, callback)
ajax(a, function() {
  ajax(b(a.somedata), function() {
    ajax(c(b.somedata), function() {
      c.finish()
    }
  }) 
})

It would be much more readable in sync mode:

sjax(a)
sjax(b(a.somedata))
sjax(c(b.somedata))
c.finish()

But Sjax is evil :) How do I do that in a nice not-so-evil and readable way?

+2  A: 

Check out this FAQ item on the jQuery site. Specially the callback reference and the complete method.

What you want is data from A to be passed to B and B's data passed to C. So you would do a callback on complete.

I haven't tried this though.

Ólafur Waage
+2  A: 

Maybe what you can do is write a server-side wrapper function. That way your javascript only does a single asynchronous call to your own web server. Then your web server uses curl (or urllib, etc.) to interact with the remote API.

Apreche
+1 for simplifying the client side
PatrikAkerstrand
+3  A: 

Don't use anonymous functions. Give them names. I don't know if you're able to do what I wrote below though:

var step_3 = function() {
    c.finish();
};

var step_2 = function(c, b) {
    ajax(c(b.somedata), step_3);
};

var step_1 = function(b, a) {
  ajax(b(a.somedata), step_2);
};

ajax(a, step_1);
Ionuț G. Stan
A: 

I believe that implementing a state machine will make the code more readable:

var state = -1;
var error = false;

$.ajax({success: function() { 
                  state = 0;
                  stateMachine(); },
        error: function() {
                  error = true;
                  stateMachine();
        }});

function stateMachine() {
  if (error) {
     // Error handling
     return;
  }

  if (state == 0) {
    state = 1;
    // Call stateMachine again in an ajax callback
  }
  else if (state == 1) {

  }
}
kgiannakakis
If possible, one should avoid the use of global variables. Jamie Rumbelow's solution is a bit more elegant.
Alsciende
+4  A: 

You could have a single function which is passed an integer to state what step the request is in, then use a switch statement to figure out what request needs to be make next:

function ajaxQueue(step) {
  switch(step) {
    case 0: $.ajax({
              type: "GET",
              url: "/some/service",
              complete: function() { ajaxQueue(1); } 
    }); break;
    case 1: $.ajax({
              type: "GET",
              url: "/some/service",
              complete: function() { ajaxQueue(2); }
            }); break;
    case 2: $.ajax({
              type: "GET",
              url: "/some/service",
              complete: function() { alert('Done!'); }
            }); break;
  }
}

ajaxQueue(0);

Hope that helps!

Jamie Rumbelow
You can't give arguments default values this way. But I like the solution.
Alsciende
Oh damn sorry, been programming a lot of PHP recently and my JavaScript's rusty. Posted updated with correct syntax :)
Jamie Rumbelow
ASP.NET 3.5's WebServices is set up like this. All the WebMethods on the JS side have a default callback function (well, if you set it up that way), and you can switch on the method name called on the server to decide what to do with the response. One thing I might change with this example is to not pass around numbers, but instead use more of an enum-like approach: ajaxQueue(some_name); so you know what your function call is doing.
Cory Larson
I like this one too. +1
Ionuț G. Stan
+4  A: 

This function should chain together a list of ajax requests, if the callbacks always return the parameters necessary for the next request:

function chainajax(params, callbacks) {
  var cb = shift(callbacks);
  params.complete = function() {
    var newparams = cb(arguments);
    if (callbacks)
      chainajax(newparams, callbacks);
  };
  $.ajax(params);
};

You can define these callback functions separately and then chain them together:

function a(data) {
  ...
  return {type: "GET", url: "/step2.php?foo"}
};
// ...
function d(data) { alert("done!"); };

chainajax({type: "GET", url: "/step1.php"},
  [a, b, c, d]);

You could also declare the functions "inline" in the call to chainajax, but that might get a little confusing.

sth