How to execute AJAX calls in order in loop using Javascript Promise

Viewed 7057

I am looping through a map, where I want to make a separate AJAX call with each map value as parameter, to fetch some data and log it. See below. This is working, but I'd like to have the AJAX calls go in order of the map. Because each call is asynchronous, so seems like I should use promises to achieve execution in order. But I am new to promises and don't really know how to do it here. I have look elsewhere on here but could not find anything. Please help.

map.forEach(function(url, key) {
   log(url);
});

function log(url) {
    $.ajax({
      url: url, 
      dataType: 'json',
      success: function (result) {
          console.log(result.value);
          console.log(result.name);
          console.log(result.action);
      }
  });
}
3 Answers

Since $.ajax returns a promise, you can use promise chaining to achieve what you want

var p = $.when();
map.forEach(function(url, key) {
    p = p.then(function() { 
        return log(url);
    });
});

function log(url) {
    return $.ajax({
        url: url, 
        dataType: 'json',
        success: function (result) {
            console.log(result.value);
            console.log(result.name);
            console.log(result.action);
        }
    });
}

Note: the code above uses only jQuery, no native promises

Or using reduce function of Array

map.reduce(function(p, url) {
    return p.then(function() { 
        return log(url);
    });
}, $.when());

If you can use ES2015+, so have native Promises,

map.reduce((p, url) => p.then(() => log(url)), Promise.resolve());

If you wanted, you can also do it like this

function log(url) {
    return $.ajax({
        url: url, 
        dataType: 'json'
    });
}

map.reduce((p, url) => p.then(results => log(url).then(result => results.concat(result))), Promise.resolve([]))
.then(results => {
    results.forEach(result => {
        console.log(result.value);
        console.log(result.name);
        console.log(result.action);
    })
});

The difference being as that all the console.log's would happen once the LAST request finished (and if any fail, none of the console log's would happen)

To chain promises should work:

function ajaxPromises(urls) {
  return Promise.all(urls.map(function(url) {
    return $.ajax({
      url: url, 
      dataType: 'json'
    })
  }))
}

Usage:

ajaxPromises(['http://yoururl.com','http://yoururl.com'])
  .then(function (result) {
    // do something with result
  })
  .catch(function (error) {
    //  do something with error
  })

If you could use async/await syntax in your project, then nothing could be easier:

async function log(url) { 
  return await 
    $.ajax({
      url: url,
      dataType: 'json',
    })
    .then(function(result) {
      console.log(result.value);
      console.log(result.name);
      console.log(result.action);
    });
}

async function run() {
  for (var i = 0; i < map.length; i++) {
    await log(map[i]);
  }
}

run();

You see, I changed forEach to for loop. It's important for await usage, because it provides (instead of forEach and other callback based loops) synchronicity of async log calls.

UPD Here is the Plunker which demonstrates such an approach.

Related