Nathan Jones
Nathan Jones

Reputation: 5174

Nested jQuery AJAX requests complete in strange order

This is a restatement of the problem I was experiencing in my previous question, I think that question suffered from the X,Y problem. I hope this question gets closer to the heart of the issue I'm experiencing. See scchange.js on Github for code.

Problem

I have an array of deferred jQuery objects that I'm calling $.when on. These are the functions that send POST requests to the server that modify data. The $.when call is wrapped in a $.get callback. This requests the data fields that I'm about to modify, satisfying the authorization requirement. Here's the code:

$.get('/admin/students/contacts/scchange/phoneTlcForm.html?frn=001' + studentsdcid, function () {
  console.log('get callback reached');
  $.when.apply($j, phoneAjaxCalls).done(function () {
    console.log('phoneAjaxCalls promises resolved');
    returnToStudentContacts();
  });
});

I am using phoneAjaxCalls.push(newPhone(tlcPhone,studentsdcid)) to push ajax calls to the array, and newPhone returns an ajax deferred object:

function newPhone(tlcPhone, studentsdcid) {
    //Create new phone
    return function() {
        return $j.ajax({
            type: 'POST',
            url: '/admin/changesrecorded.white.html',
            data: tlcPhone
        });    
    };
}

When this code is run, I see in the Console that "get callback reached" is printed before "phoneAjaxCalls promises resolved", which suggests that the requests are getting sent in the order that they are shown in the code. However, Chrome's DevTools Network tab seems to suggest that the phoneAjaxCalls network requests are getting completed before the phoneTlcForm.html document is loaded.

enter image description here

The first 6 requests are all phoneAjaxCalls requests, and the last request is phoneTlcForm.html from the $.get, but that should be first!

It appears that the backend is receiving these requests in the same order as the Network tab is showing, because I'm getting authorization errors in the responses to those phoneAjaxCalls requests.

What needs to happen is that the request to phoneTlcForm.html should complete before any of the phoneAjaxCalls requests are made.

Background

My project is a plugin, so I can't modify the backend logic to make this process easier. The system that I'm interfacing with has certain authorization requirements when creating or updating data. The backend system must "see" that the data fields that I want to modify were rendered in an HTML page before the requests that modify data are sent. This is why I need the phoneTlcForm.html page to load before the other requests.

Upvotes: 1

Views: 214

Answers (1)

mati
mati

Reputation: 5348

What's happening here is that when you build the phoneAjaxCalls array, you call to $j.ajax for each individual item. Each request will be made as soon as you do that call.
I guess that by the time you call $j.when.apply($j, phoneAjaxCalls), most of the promises there will be already resolved, and the done callback called right away.

Let me try to illustrate with some comments:

// previous ajax requests already fired!
$j.get('/admin/students/contacts/scchange/phoneTlcForm.html?frn=001' + studentsdcid, function () {
  console.log('get callback reached');
  // inspect the state of phoneAjaxCalls here
  // they might be resolved already.

  // you want to exec those ajax calls here, not before!
  $j.when.apply($j, phoneAjaxCalls).done(function () {
    console.log('phoneAjaxCalls promises resolved');
    returnToStudentContacts();
  });
});

So from here you have differents way to solve it. I think you already got a good response in the question you linked.

In each place you return $j.ajax, you could return a function wrapping that instead, to avoid the execution. Let's take the updateEmail function as an example:

return function(){
    return $j.ajax({
            url: '/ws/schema/table/' + config.contactsEmailTable + '/' + emailRecordId,
            data: JSON.stringify(emailUpdateData),
            dataType: 'json',
            contentType: "application/json; charset=utf-8",
            type: 'PUT'
        });
 };

After wrapping all those, you can call them like this:

$.get('/admin/students/contacts/scchange/phoneTlcForm.html?frn=001' + studentsdcid, function () {
  var phoneAjaxCallsPromises = $.map(phoneAjaxCalls, function(c){
    return c();
  });
  $.when.apply($j, phoneAjaxCalls).done(function () {
    console.log('phoneAjaxCalls promises resolved');
    returnToStudentContacts();
  });
}); 

This solution feels like a hack, but understanding the problem might enable you to implement the proper fix.

Upvotes: 2

Related Questions