1

I have function that search for every element with a specific class:

$("#stepSurveyCtnId .questionCtnClass").each(function () {}

Inside each step, I check if a question is of type customer:

var type = $(this).children().data("question-type");

var isCustomerQuestion = false;

switch (type) {
    case "Name":
    case "Email":
        isCustomerQuestion = true;
        break;
}

If it's customer type, I get the next id of the customer's table from the database:

  if(isCustomerQuestion) {
      if (customerId == -1) {
          $.ajax({
              method: "POST",
              url: urlCustomerCreate, 
              success: function (ajaxData) {
                  customerId = ajaxData.NumericValue;
              }
          });
      } 
  }

The issue is that in the second iteration of the .each() function, customerId is still = -1, when it should be 1305 for example.

It seems that the execution don't stop in the $.ajax call, or the iterations are executed at the same time and the second iteration don't receive the customerId from the first iteration.

Patrick
  • 2,995
  • 14
  • 64
  • 125
  • 2
    AJAX = * Asynchronous * Javascript And Xml . The success function will not be called until some unknown future time. The success function is where you should continue your logic for processing one customer. – Dave S Aug 10 '18 at 23:04
  • Hi, thanks! Sorry but I did not understand your comment :( – Patrick Aug 10 '18 at 23:08
  • 1
    I might be misreading the logic of your code fragments, but it sounds like you are saying: `for each x { if x = customer { make AJAX call; try to use results right here } } }` but the AJAX result is not available yet. "success" will not be called until some later time. success should be a call to "process_one_customer()" that does *all* the work not just setting ID. – Dave S Aug 10 '18 at 23:11
  • Yes, that's right. In the console I get "customerId == -1" two times right away for 2 iterations – Patrick Aug 10 '18 at 23:13
  • It might be that success (and error) should be where you continue your loop. – Dave S Aug 10 '18 at 23:14
  • It does not stop the execution in the ajax call. It goes right to the next .each step. – Patrick Aug 10 '18 at 23:24
  • 1
    That's right, JavaScript is calling your database asynchronously, meaning it will not stop execution. Your success function is triggered when the database comes back with the answer, which will be after the iterations of your loop are completed. – Jamie Aug 11 '18 at 00:03
  • What you are doing seems odd, accessing the result of a database call in the next iteration is not something I see often, what exactly are you trying to do that requires the customerId on the next iteration? – Jamie Aug 11 '18 at 00:07
  • @JamieDay Hi, thanks! I need the customerId to update the related customer fields inside the customer's table with the same record id, so I can merge all information of the page into a single database record. – Patrick Aug 11 '18 at 00:09
  • 1
    Gotcha. @Shubham's answer is the right idea, but I can try and come up with an answer for your particular case using jQuery – Jamie Aug 11 '18 at 00:16
  • You can disable the async behavior, this is probably a fitting answer to your question: https://stackoverflow.com/a/133327/7362396 - However, as others here and also [this](https://stackoverflow.com/a/27612493/7362396) have mentioned this is not a good idea and you should rewrite your logic to properly work async. – Tobias K. Aug 14 '18 at 12:21
  • @TobiasK Hi, thanks. async: false is not allowed anymore unfortunately mainly by browsers like Chrome I think. – Patrick Aug 14 '18 at 17:08

5 Answers5

5

I'm still not 100% clear on sure on how everything is structured for you, but here is one way of handling asynchronicity in JavaScript (adapted from @ShubHam's answer)

function handleQuestion(questionElements, index, customerId) {
    if (questionIndex >= questionElements.length) return;

    var type = $(this).children().data("question-type");

    var isCustomerQuestion = false;

    switch (type) {
    case "Name":
    case "Email":
        isCustomerQuestion = true;
        break;
    }

    if(isCustomerQuestion) {
      if (customerId == -1) {
          $.ajax({
              method: "POST",
              url: urlCustomerCreate, 
              success: function (ajaxData) {
                  handleQuestion(questionElements, questionIndex + 1, ajaxData.NumericValue);
              }
          });
      } else {
        // Have ID now
        handleQuestion(questionElements, questionIndex + 1, customerId);
      }
  }
}

// Go
handleQuestion($("#stepSurveyCtnId .questionCtnClass"), 0, -1);

This will only continue to the next iteration after the success callback has been triggered.

Jamie
  • 230
  • 1
  • 11
3

Put logic inside one function (say function 1) and ajax call inside other function.

Call ajax function from function 1. Inside success call function 1 with required params

Update (example added):

var x=['a','b','c']
var elm=document.getElementById('demo')
x.forEach(function(temp){
elm.innerHTML=elm.innerHTML+temp
})
<div id='demo'></div>

This can be converted to new logic as

var x=['a','b','c']
function sethtml(temp,length,maxlength){
  //here ajax call can be placed
  var elm=document.getElementById('demo')
  elm.innerHTML=elm.innerHTML+temp
  //inside success function of ajax
  traverse(length+1,maxlength)
}

function traverse(length,maxlength){
  if(length>=maxlength)
  {
    //all calls done next steps to perform
  }else{
    sethtml(x[length],length,maxlength)
  }
}

traverse(0,x.length)
<div id='demo'></div>

Advice to be considered from Jamie-Day in comments: Check your logic for scope of improvement. Accessing db results in for each kind of scenario generally can be avoided(ideally it should be avoided for better user experience)

Shubham
  • 1,288
  • 2
  • 11
  • 32
2

Change your ajax code. add "async: false" so that each code next to ajax will wait for ajax result

  if(isCustomerQuestion) {
      if (customerId == -1) {
          $.ajax({
              method: "POST",
              async: false,
              url: urlCustomerCreate, 
              success: function (ajaxData) {
                  customerId = ajaxData.NumericValue;
              }
          });
      } 
  }
swathi_sri
  • 417
  • 4
  • 8
  • 2
    You should probably add a warning to say that it's going to block everything while waiting for the answer and thus it's not an ideal solution, even though it's the simplest – Axnyff Aug 18 '18 at 21:34
1

The first A in AJAX stands for Asynchronous which means that the ajax calls would get executed and would not wait for the call to finish. This way we can let users interact with other elements on the page and provide a good user experience.

If we make the AJAX calls asynchronous by setting the async option to false, then the browser would wait for the ajax call to complete and users would not be able to interact with the elements until the call has completed. With the increase in number of calls, this blockage time would increase.

I would suggest you find a better alternative than this.

Sheikh Azad
  • 353
  • 2
  • 11
1

First, you need to think asynchronously. Code that need to run after the ajax should be called from the success function. You also want to add error function to handle server errors.

Second, to improve speed and bandwidth I'd reduce number of AJAX calls to a single one, by joining all IDs together in a single AJAX request. It require server-side changes and you did not provide the server-side, so I'll leave server side to you.

// Prepare ajax call
var customerData = [];
var customerCreateData = [];
$("#stepSurveyCtnId .questionCtnClass").each(function () {
    var type = $(this).children().data("question-type");
    var isCustomerQuestion = false;
    switch (type) {
        case "Name":
        case "Email":
            isCustomerQuestion = true;
            break;
    }

    // Set customerId and customerCreateData

    if(isCustomerQuestion) {
        if (customerId == -1) {
            customerCreateData.push(customerCreateData);
        }
    }
}); // end each

if (customerCreateData.length) {
      $.ajax({
              method: "POST",
              url: urlCustomerCreate,
              data: customerCreateData,
              success: function (ajaxData) {
                  customerData = ajaxData.customerData;
                  doAmazingThingsWithCustomers(customerData);
              },
              error: function(jqXHR, textStatus, errorThrown) {
                  alert('Server error: ' + errorThrown);
              }
          });
}
oriadam
  • 7,747
  • 2
  • 50
  • 48
  • Hi thanks! After more detail testing, I have detecting an issue, when I get the ajaxData.customerData from the ajax call, already 3 iterations from the .each has pass and I loss 3 customer information :( – Patrick Aug 20 '18 at 23:41
  • @Patrick Yes that's how async work. You should experiment more with the async principle, perhaps read or see videos about AJAX etc – oriadam Aug 22 '18 at 07:24
  • So your answer is not a solution for my problem, that’s what your are saying? – Patrick Aug 22 '18 at 15:30
  • @Patrick lol that depends on how you define the problem :) If you want AJAX calls to be called synchronously, just add `async:false` to your calls. If you want your page to work well, you'd get better results by working with AJAX as intended :) I suggested one solution approach, and there are many other possible solutions. – oriadam Aug 26 '18 at 09:15
  • async: false is not suported anymore. – Patrick Aug 26 '18 at 10:33
  • I'm not sure that the use of `async:false` is completely deprecated, perhaps only use of `async:false` together with `.done()` is deprecated. What do you understand from this sentence -- `As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done()` – oriadam Aug 29 '18 at 06:52
  • @Jamie Yes I did, but I have preferred oriadam's answer but the fact is that it's not working like expected unfortunately. I have to run more tests to be sure. – Patrick Aug 29 '18 at 21:36
  • @oriadam I have tested the sentence with and without .done, with and without async: false and I have not been able to put it working. – Patrick Aug 29 '18 at 21:38
  • @Patrick what exactly isn't working? can you create a codepen or jsfiddle? Also include server side if relevant – oriadam Aug 30 '18 at 07:28