defnull
defnull

Reputation: 4199

How to chain ajax requests?

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?

Upvotes: 31

Views: 17377

Answers (9)

Endless
Endless

Reputation: 37975

I made a method using Promises

// How to setup a chainable queue method
var sequence = Promise.resolve();

function chain(next){
    var promise = new Promise(function(resolve){
        sequence.then(function(){
            next(resolve);
        });	
    });

    sequence = promise;
}

// How to use it
chain(function(next){
    document.write("<p>start getting config.json</p>");
    setTimeout(function(){
    	document.write("<p>Done fetching config.json</p>");
        next();
    }, 3000);
});

chain(function(next){
    document.write("<p>start getting init.js</p>")
    setTimeout(function(){
        document.write("<p>starting eval scripting</p>");
        next();
    }, 3000);
});

chain(function(next){
    document.write("<p>Everything is done</p>");
});


Bonus: A ultraligth 138 byte limited A- Promise (that can only resolve - without parameters, and only call the last then-method )

Background: I made this for node.js at the point where it dose not have promises ATM. I didn't want a complete full blown Promise library that I was dependent on and had to include in my package.json, I needed it to be fast and light and do mostly one thing only. I only needed it for one thing (chaining things like you want to)

function Q(a,b){b=this;a(function(){b.then&&b.then();b.then=i});return b}function i(a){a&&a()}Q.prototype={then:function(a){this.then=a}};

How?

// Start with a resolved object
var promise = new Q(function(a){a()});
// equal to 
// var promise = Promise.resolve();

// example usage
new Q(function(resolve){
    // do some async stuff that takes time
    // setTimeout(resolve, 3000);
}).then(function(){
    // its done
    // can not return a new Promise
}); // <- can not add more then's (it only register the last one)

and for the chainable queue method

// How to setup a chainable queue method with ultraligth promise
var sequence = new Q(function(a){a()});

function chain(next){
    var promise = new Q(function(resolve){
        sequence.then(function(){
            next(resolve);
        }); 
    });

    sequence = promise;
}

Upvotes: 1

DrewT
DrewT

Reputation: 5082

The complete callback is what you're looking for:

$.ajax({
     type: 'post',
     url: "www.example.com",
     data: {/* Data to be sent to the server. It is converted to a query string, if not already a string. It's appended to the url for GET-requests. */},
     success:
          function(data) {
              /* you can also chain requests here. will be fired if initial request is successful but will be fired before completion. */
          },
    complete: 
         function() {
             /* For more a more synchronous approach use this callback. Will be fired when first function is completed. */
         }
});

Upvotes: 0

Diego
Diego

Reputation: 4433

Update: I've learn a better answer for this if you are using jQuery, see my update under the title: Using jQuery Deffered

Old answer:

You can also use Array.reduceRight (when it's available) to wrap the $.ajax calls and transform a list like: [resource1, resource2] into $.ajax({url:resource1,success: function(...) { $ajax({url: resource2... (a trick that I've learn from Haskell and it's fold/foldRight function).

Here is an example:

var withResources = function(resources, callback) {
    var responses = [];
    var chainedAjaxCalls = resources.reduceRight(function(previousValue, currentValue, index, array) {
        return function() {
            $.ajax({url: currentValue, success: function(data) {
                responses.push(data);
                previousValue();
            }})
        }
    }, function() { callback.apply(null, responses); });
    chainedAjaxCalls();
};

Then you can use:

withResources(['/api/resource1', '/api/resource2'], function(response1, response2) {
    // called only if the ajax call is successful with resource1 and resource2
});

Using jQuery Deffered

If you are using jQuery, you can take advantage of jQuery Deffered, by using the jQuery.when() function:

 jQuery.when($.get('/api/one'), $.get('/api/two'))
       .done(function(result1, result2) { 
              /* one and two is done */
        });

Upvotes: 3

Jamie Rumbelow
Jamie Rumbelow

Reputation: 5095

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!

Upvotes: 24

sth
sth

Reputation: 229934

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.

Upvotes: 5

kgiannakakis
kgiannakakis

Reputation: 104196

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) {

  }
}

Upvotes: 2

Ionuț G. Stan
Ionuț G. Stan

Reputation: 179219

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);

Upvotes: 17

Apreche
Apreche

Reputation: 32999

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.

Upvotes: 4

&#211;lafur Waage
&#211;lafur Waage

Reputation: 70041

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.

Upvotes: 2

Related Questions