Ravioli87
Ravioli87

Reputation: 835

AJAX Strategy for Running Tasks in Order

I have an API call which returns a list of building departments in order. On the success of the call, a second API call is made to retrieve the hours of each department. The hours are then placed into a table for each department.

The API calls do indeed result in the proper hours being fetched for the department and in the right order, but creation of the HTML tables shows the list of departments is out of order, presumably due to the asynchronous nature of AJAX.

A sample console.log would be (for the first call):

getting area 3 which is in position 0
getting area 10 which is in position 1
getting area 8 which is in position 2 
getting area 9 which is in position 3
getting area 7 which is in position 4
getting area 6 which is in position 5
getting area 5 which is in position 6
getting area 4 which is in position 7

Great! Upon retrieval of the hours, however the order does not hold:

Success for area 2
Success for area 7
Success for area 5
... etc ...

The department list is shown completely out of order on the page. On one page load, area 8 could show first (position 2). And on another, area 7 could show first (position 4).

Here is the first call to areas:

/**
 *  Get the areas from the server 
 *
 *  @var bool  Optionally display area hours for the week
 */
function getAreas(displayAreas){
   ajaxObject = {
      url: 'http://example.com/api/hoursrest/areas',
      method: 'GET',
      dataType: 'json',
      xhrFields: {
        withCredentials: true
      },
      crossDomain: true,
   }
   $.ajax(ajaxObject)
        .success(function(data,status,xhr) {
            $('#main_hours').html(''); //clear previous hours
            for(var i = 0; i < data.length; i++){   
                //get and display the hours for this week
                console.log("getting area " + data[i].id + " which is in position " + i)
                getAreaHoursByWeek(data[i].id, today);
            }
        })
        .fail(function(data,status,xhr){
        })
        .always(function(data,status,xhr){
        });
}

And the next call to fetch the hours:

/**
 * Get hours for a given area during a given week.
 * Specify a date and the hours for that date's week will be returned.
 *
 * @var int area  The area's id.
 * @var String date  The date in YYYY-mm-dd format
 */
function getAreaHoursByWeek(area, date){
    //send area, start and end date to the server
    ajaxObject = {
      url: 'http://example.com/api/hoursrest/areadaterange',
      method: 'GET',
      dataType: 'json',
      xhrFields: {
        withCredentials: true
      },
      crossDomain: true,
      data: {'areaId': area, 'startDate': moment(date).startOf('week').format('YYYY-MM-DD'), 'endDate': moment(date).endOf('week').format('YYYY-MM-DD')}
   }
   $.ajax(ajaxObject)
        .success(function(data,status,xhr) {
            //display area's hours
            console.log("Success for area " + area)
            $('#main_hours').append(displayAreaHours(data)); //append area's hours to the main_hours div
        })
        .fail(function(data,status,xhr){
        })
        .always(function(data,status,xhr){
        });
}

The function that assembles the table is not asynchronous, but here is the stub for clarification.

/**
 * Display the hours for a given area
 *
 * @var JSON hours  JSON formatted hours for an area
 */ 
function displayAreaHours(hours){
    ...
    return display;
}

Upvotes: 0

Views: 38

Answers (1)

charlietfl
charlietfl

Reputation: 171690

You want to use a promise approach. Following uses sample data from https://jsonplaceholder.typicode.com/ which has a collection of users and each user has a number of content posts.

Basically it creates an array of request promises for all of the users content posts requests and uses $.when() to create a promise that resolves after all of the individual requests have resolved

jsfiddle demo

function getUsers() {
  return $.ajax({ // return $.ajax` promise
    url: 'https://jsonplaceholder.typicode.com/users',
    dataType: 'json'
  }).then(function(users) {
    // create array of request promises
    var promises = users.map(getUserPosts);
    // return promise that resolves when all request promises resolve
    return $.when.apply(null, promises).then(function() {
      // return updated users array to next then()
      return users;
    });
  });
}

function getUserPosts(user) {
  return $.ajax({ // return $.ajax` promise
    url: 'https://jsonplaceholder.typicode.com/posts',
    dataType: 'json',
    data: {
      userId: user.id,
      _limit: 2
    }
  }).then(function(posts) {
    console.log('Received posts userId:', user.id);
    // add posts for this user as property of the user object
    user.posts = posts;
    return user;
  });
}

// start requests
getUsers().then(processUsers)
  .fail(function() {
    console.log('Something went wrong in at least one request');
  });

function processUsers(users) {
  console.log('All users done');
  var $cont = $('#container');
  // insert data in dom
  $.each(users, function(_, user) {
    var $div = $('<div>', {class: 'user'});
    $div.append('<strong>User Id:' + user.id + '<strong>');
    // loop over this user's posts
    $.each(user.posts, function(_, post) {
      $div.append($('<div>').text('Post Id : ' + post.id))
    });
    $cont.append($div)
  });
}
.user {
  border: 2px solid #ccc;
  margin: 5px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container"></div>

Upvotes: 1

Related Questions