83

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)})
mdinger
  • 85
  • 1
  • 2
  • 8
John Mcdock
  • 1,219
  • 2
  • 11
  • 19
  • Looks like pretty much an exact duplicate of [How to chain ajax calls using jquery](http://stackoverflow.com/questions/8612894/how-to-chain-ajax-calls-using-jquery) – jfriend00 Apr 16 '13 at 00:46
  • 1
    `sniker` jQuery.Promise().Promise().Promise(void) – Jay Apr 16 '13 at 00:48
  • 3
    The solution in the "duplicate" question is I think old and obselete. – John Mcdock Apr 16 '13 at 01:19
  • 1
    Yeah, but the suggestion to use q from the update is a good one: https://github.com/kriskowal/q. For more complicated stuff, seq is worth a look too: https://github.com/substack/node-seq/ – Milimetric Nov 05 '13 at 13:53

9 Answers9

97

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(...)).

Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44
  • 8
    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. */ }); – Jerinaw Jan 09 '14 at 18:50
  • 1
    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. – Beetroot-Beetroot Jan 09 '14 at 19:13
  • 2
    just checked with jquery 1.11.0 and all then calls get arguments from when and not from previous then. – pajics Apr 11 '14 at 12:29
  • 1
    @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? – Beetroot-Beetroot Apr 13 '14 at 00:08
  • 1
    Unfortunately I cant recreate my code with fiddle (using gmaps) but when I tried something simple like this: http://jsfiddle.net/CbPbw/1/ it worked as expected. I'll have to find out what's wrong with my code. Thanks – pajics Apr 14 '14 at 09:42
  • 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. – Beetroot-Beetroot Apr 14 '14 at 14:20
  • @Beetroot-Beetroot - where do you place the `done` method for each promise separately? I would imagine `$.when(first()).done(..).then(second).done(...).then(third);` – vsync Mar 15 '16 at 16:29
  • 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 `third` – vsync Sep 26 '18 at 18:10
  • 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). – Dave Cluderay Aug 19 '21 at 11:04
  • 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? – garek007 Sep 09 '22 at 18:16
46

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/

user3242460
  • 653
  • 5
  • 2
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.

prajnavantha
  • 1,111
  • 13
  • 17
16

It's much simpler than that.

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

function first() {
    return $.ajax(...);
}
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • @SLaks Is it possible to have more than one deferred in the then? As in `first().then(second, third).then(fourth);` ? – Mark Pieszak - Trilon.io Apr 16 '13 at 01:07
  • 2
    How do I chain multiple ajax calls and pass the return value? deferred.resolve used to be the way I used to do it. – John Mcdock Apr 16 '13 at 01:20
  • @JohnMcdock: Return a value from `.then()` (in 1.8+). See http://api.jquery.com/deferred.then/ – SLaks Apr 16 '13 at 03:50
  • 6
    @SLaks - 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 functions – vsync Jun 16 '14 at 14:45
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.

Duncan Lukkenaer
  • 12,050
  • 13
  • 64
  • 97
  • 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. – Nick Johnson Apr 07 '20 at 03:31
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)
Mikhail Vitik
  • 506
  • 7
  • 10
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, ...);
}
MarSoft
  • 3,555
  • 1
  • 33
  • 38
  • 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. – MarSoft May 02 '16 at 21:37
  • It worked for me ! I only needed to change *function next()* to *function next(ret)*, becaouse ret was not defined :) – ilian6806 Feb 18 '19 at 12:38
  • Thanks @ilian6806, fixed. – MarSoft Feb 19 '19 at 09:47
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>
duanev
  • 936
  • 13
  • 17
-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();
     })
   });
Dimitris Kougioumtzis
  • 2,339
  • 1
  • 25
  • 36