How do I chain three asynchronous calls using jQuery promises?
Asked Answered
C

9

85

I have three HTTP calls that need I need to make in a synchronous manner and how do I pass data from one call to the other?

function first()
{
   ajax()
}

function second()
{
   ajax()
}

function third()
{
   ajax()
}


function main()
{
    first().then(second).then(third)
}

I tried to use the deferred for the two functions and I came up with a partial solution. Can I extend it to be for three functions?

function first() {
    var deferred = $.Deferred();
     $.ajax({

             "success": function (resp)
             {

                 deferred.resolve(resp);
             },

         });
    return deferred.promise();
}

function second(foo) {
     $.ajax({
            "success": function (resp)
            {
            },
            "error": function (resp)
            {
            }
        });
}


first().then(function(foo){second(foo)})
Celebration answered 16/4, 2013 at 0:44 Comment(4)
Looks like pretty much an exact duplicate of How to chain ajax calls using jqueryLatreshia
sniker jQuery.Promise().Promise().Promise(void)Nuncle
The solution in the "duplicate" question is I think old and obselete.Celebration
Yeah, but the suggestion to use q from the update is a good one: github.com/kriskowal/q. For more complicated stuff, seq is worth a look too: github.com/substack/node-seqUnderbred
L
99

In each case, return the jqXHR object returned by $.ajax().

These objects are Promise-compatible so can be chained with .then()/.done()/.fail()/.always().

.then() is the one you want in this case, exactly as in the question.

function first() {
   return $.ajax(...);
}

function second(data, textStatus, jqXHR) {
   return $.ajax(...);
}

function third(data, textStatus, jqXHR) {
   return $.ajax(...);
}

function main() {
    first().then(second).then(third);
}

Arguments data, textStatus and jqXHR arise from the $.ajax() call in the previous function, ie. first() feeds second() and second() feeds third().

DEMO (with $.when('foo') to deliver a fulfilled promise, in place of $.ajax(...)).

Leatrice answered 16/4, 2013 at 19:43 Comment(10)
This doesn't work for me. When I try you example the results from first are passed to second AND third. So first feeds second and third. Not first feeds second, second feeds third. $.ajax({...}).then(function(..){ return $.ajax({...}); }).then( function(...){ /* I want the results from the second call. */ });Incarnadine
No time right now to run a test but I can't see why the code in my answer shouldn't work. Make sure you are using jQuery 1.8.3 or above. Earlier versions may well give the symptom you describe.Leatrice
just checked with jquery 1.11.0 and all then calls get arguments from when and not from previous then.Salutation
@pajics, I have added a demo to the answer. Either 1.11.0 has reverted or you are doing something wrong. Can you provide a fiddle?Leatrice
Unfortunately I cant recreate my code with fiddle (using gmaps) but when I tried something simple like this: jsfiddle.net/CbPbw/1 it worked as expected. I'll have to find out what's wrong with my code. ThanksSalutation
To make absolutely sure you are using 1.11, alert($.fn.jquery); at the point in your code where you rely on 1.11- ie where you are trying to get your promise chain working.Leatrice
@Leatrice - where do you place the done method for each promise separately? I would imagine $.when(first()).done(..).then(second).done(...).then(third);Mv
What if second needs to be re-called until the response is what you expect? for example {state:"running"} and you must use timeout to re-call second until {state:"ready"} and only then you can continue to thirdMv
Note that in jquery versions earlier than 1.8, you will need to use .pipe() instead of .then() in order to get the expected chaining behaviour (i.e. where the results from the previous call feed into the next).Bodoni
This worked for me. Question, is there a way to pass additional parameters in the .then instead of just the data from the first ajax function?Flosser
E
47

There is actually a much easier approach when using promises with jQuery. Have a look at the following:

$.when(
    $.ajax("/first/call"),
    $.ajax("/second/call"),
    $.ajax("/third/call")
    )
    .done(function(first_call, second_call, third_call){
        //do something
    })
    .fail(function(){
        //handle errors
    });

Simply chain all your calls into the $.when(...) call and handle the return values in the .done(...) call.

Here's a walkthrough if you prefer: http://collaboradev.com/2014/01/27/understanding-javascript-promises-in-jquery/

Exportation answered 27/1, 2014 at 23:21 Comment(7)
One question about this. What if "/first/call" returns an ID that I need in the second call "second/call/id"?? In this case I need to get first the result of the first call, then the second.Parrott
@Emilio, yes precisely. That is what the OP asked for and this answer does not deliver in that regard.Leatrice
DV: doesn't answer the question - misleadingLippizaner
Not the answer the OP was looking for, but the one I was. Thanks!Propylaeum
Answers a completely different question. Recommend removing in order to avoid confusion.Dre
The three ajax calls in this example will happen asynchronouslyPilaf
This answer doesn't chain the requests, but are rather run in parallel.Matthews
P
38

Quite late to reply, but I guess answers are missing some straight forward code for chaining. Chaining events is pretty simple with promise support in jquery. I use the following for chaining:

$.ajax()
.then(function(){
   return $.ajax() //second ajax call
})
.then(function(){
   return $.ajax() //third ajax call
})
.done(function(resp){
   //handle final response here
 })

It's simple with no complicated for loops or multiple nested callbacks.

Poignant answered 2/4, 2017 at 20:56 Comment(1)
Which arguments will be passed to the anonymous functions in your example? The response from the server?Alasteir
S
16

It's much simpler than that.

$.ajax already returns a promise (Deferred object), so you can simply write

function first() {
    return $.ajax(...);
}
Schreck answered 16/4, 2013 at 0:47 Comment(4)
@Schreck Is it possible to have more than one deferred in the then? As in first().then(second, third).then(fourth); ?Eijkman
How do I chain multiple ajax calls and pass the return value? deferred.resolve used to be the way I used to do it.Celebration
@JohnMcdock: Return a value from .then() (in 1.8+). See api.jquery.com/deferred.thenSchreck
@Schreck - please extend your answer to better explain what is asked, since it seems your answer is a very important one to the nature and basics of jQuery ajax object, it deserves a few more lines which explains how to chain multiple async functionsMv
B
9

The best way to do this is by making a reusable function for this. This can even be done with just one line of code using reduce:

function chainPromises(list) {
    return list.reduce((chain, func) => chain ? chain.then(func) : func(), null);
}

This function accepts an array of callbacks which return a promise object, like your three functions.

Example usage:

chainPromises([first, second, third]).then(function (result) {
    console.log('All done! ', result);
});

This way the result of first will also automatically be the parameter of second, so basically what happens is this:

first().then(function(res1) { return second(res1) })
       .then(function(res2) { return third(res2)  })
       .then(function(result) { console.log('All done! ', result) });

And of course you could add as many functions to the array as you want.

Behah answered 29/1, 2017 at 12:52 Comment(1)
By far the best solution. I picked up the snippet and integrated it into a library requiring groupings of chained Ajax events. Sadly, I had to change the ES6 syntax, but still a clean solution. Well done. I hope more people vote this one up.Frigid
V
6

You can write it in more functional manner:

[function() { return ajax(...)}, function(data) { return ajax(...)}]
.reduce(function(chain, callback) { 
  if(chain) { 
    return chain.then(function(data) { return callback(data); });
  } else {
    return callback();
  }
}, null)
Ventage answered 26/1, 2016 at 15:48 Comment(0)
S
4

I found a good looking solution here: How do I chain a sequence of deferred functions in jQuery 1.8.x?

And here is my own implementation of similar approach, somewhat ugly but probably working. It broadcasts result of each method as a «progress update» on returned promise object.

  $.chain = function() {
      var defer = $.Deferred();
      var funcs = arguments;
      var left = funcs.length;
      function next(lastResult) {
          if(left == 0) {
              defer.resolve();
              return;
          }
          var func = funcs[funcs.length - left]; // current func
          var prom = func(lastResult).promise(); // for promise will return itself,
                                       // for jquery ojbect will return promise.
          // these handlers will be launched in order we specify them
          prom.always(function() {
              left--;
          }).done(function(ret) {
              defer.notify({
                  idx: funcs.length-left,
                  left: left,
                  result: ret,
                  success: true,
              });
          }).fail(function(ret) {
              defer.notify({
                  idx: funcs.length-left,
                  left: left,
                  result: ret,
                  success: false,
              });
          }).always(function(ret) {
              next(ret);
          });
      }
      next();
      return defer.promise();
  };

How to use it for your situation? Maybe not beautiful, but it should work:

function first() {
    return ajax(...);
}

var id;

funciton second() {
    return ajax(id, ...);
}

function third() {
    return ajax(id, ...);
}

$.chain(first, second, third).progress(function(p) {
    if(p.func == first)
        id = p.result.identifier;
}).then(function() {
    alert('everything is done');
});

Or you can just assign that id variable from first function.

Or if you only need previous function's result, you can use this approach:

function first() {
    return ajax(...);
}
function second(first_ret) {
    return ajax(first_ret.id, ...);
}
function third(second_ret) {
    return ajax(second_ret.something, ...);
}
Sabinesabino answered 23/9, 2015 at 0:33 Comment(3)
When you call a notify() method on a Deferred object, you pass an object to it. Then anyone who subscribed to progress updates of that Deferred's promise will be called with that object.Sabinesabino
It worked for me ! I only needed to change function next() to function next(ret), becaouse ret was not defined :)Verdun
Thanks @ilian6806, fixed.Sabinesabino
C
0

The following appears to work and allows the list of functions to be dynamic:

<html>
  <head>
  <title>demo chained synchronous calls</title>
  </head>
  <body>

  <script src="http://code.jquery.com/jquery-2.2.4.min.js"></script>
  <script type="text/javascript">
    function one(parms) {
        console.log('func one ' + parms);
        return 1;
    }

    function two(parms) {
        console.log('func two ' + parms);
        return 2;
    }

    function three(parms) {
        console.log('func three ' + parms);
        return 3;
    }

    function four(parms) {
        console.log('func four ' + parms);
        return 4;
    }

    var funcs = ['one', 'two', 'three', 'four'];
    var rvals = [0];

    function call_next_func() {
        if (funcs.length == 0) {
            console.log('done');
        } else {
            var funcname = funcs.shift();
            console.log(funcname);
            rvals.push(window[funcname](rvals));
            call_next_func();
        }
    }

    $(document).ready(function($){
        call_next_func();
    });
  </script>

  </body>
</html>
Communion answered 2/12, 2016 at 23:25 Comment(0)
P
-2

To chain jquery ajax calls i did :

function A(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   function B(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   function C(){
     return $.ajax({
      url: url,
      type: type,
      data: data,
      datatype: datatype,
      success: function(data)
      {
        code here
      }
    });
   }

   A().done(function(data){
     B().done(function(data){
        C();
     })
   });
Prophet answered 18/6, 2018 at 5:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.