1

I am writing a function to use ajax to get the instructions from a back end server while the page is loading. My ajax code fetches the instructions based on the number and prints the instructions using the variable response.setText in this <p id="loadText"></p> element using jquery and the console. Here is my ajax function:

function ajaxFetch(s) {
    var success = false;
    $.ajax({
        type: "POST",
        url: "post.php",
        data: {
            step: s
        },
        async: false,
        dataType: 'JSON',
        success: function (response) {
            $("#loadText").text(response.stepText);
            console.log(response.stepText);
            success = true;
        }
    });
    return success;
}

I am trying to use another function to loop through steps no matter how many there are, but here are my problems that I keep running into:

  • ajaxFetch() is not updating the DOM until last execution
  • tried setTimeout() and not updating DOM
  • for loop looping through ajaxFetch() too quickly
  • response.stepText prints in the console on time, but does not update DOM on time

Here is a sample loop I have tried:

function uploadSteps(maxStep) {
   for (var x = 1; x <= maxStep; x++){
       setTimeout(ajaxFetch(x), 20);
   }
}

Sorry this is so long and thanks in advance.

Will
  • 122
  • 1
  • 9

2 Answers2

3

By the time your for-loop completes, say 20 iterations, your ajax call in ajaxFetch only would have received the response for the first few calls and what you see in the end is the response for the last ajax call. You can use this link to understand how async calls work in javascript https://rowanmanning.com/posts/javascript-for-beginners-async/

So the answer is, you need to wait till the first ajax call completes and then call the method again with a timeout of 20ms, like this

var globalMaxSteps = 1;
var startIndex = 1;

function ajaxFetch(s) {
    $.ajax({
        type: "POST",
        url: "post.php",
        data: {
            step: s
        },
        async: false,
        dataType: 'JSON',
        success: function (response) {
            $("#loadText").text(response.stepText);
            console.log(response.stepText);
            startIndex++;
            if(startIndex <= globalMaxSteps) {
           setTimeout(function(){
           ajaxFetch((startIndex); 
               },20);
        } else {
               console.log("All Iterations complete");
        }

        }
    });
}


function uploadSteps(maxStep) {
   startIndex = 1;
   globalMaxSteps = maxStep;
   setTimeout(function(){
    ajaxFetch(startIndex); 
   },20);
}
Ajanth
  • 2,435
  • 3
  • 20
  • 23
2

First, we need to fix mistakes in the uploadSteps function:

function uploadSteps(maxStep) {
   // here change `var x` to `let x` to avoid problems 
   // like here - https://stackoverflow.com/q/750486/5811984
   for (let x = 1; x <= maxStep; x++){
       setTimeout(function() {
           // notice how here ajaxFetch(x) is wrapped into a function, 
           // otherwise it gets called right away
           ajaxFetch(x)
       }, 20);
   }
} 

Now here's another problem - all the setTimeout will be called with 20ms delay, that means that all of them will be executed at the same time, but ~20ms after uploadSteps() was called.

Let's see what happens when maxStep=3 (assuming your CPU is very fast because that is irrelevant for understanding the problem):

Time passed | what happens
--------------------------
0ms         | setTimeout(ajaxFetch(1), 20) is called
0ms         | setTimeout(ajaxFetch(2), 20) is called
0ms         | setTimeout(ajaxFetch(3), 20) is called
20ms        | ajaxFetch(1) is called
20ms        | ajaxFetch(2) is called
20ms        | ajaxFetch(3) is called

So as you see all ajaxFetch's are called at the same time, and I am assuming that's not exactly what you need. What you might be looking for is this:

Time passed | what happens
--------------------------
0ms         | setTimeout(ajaxFetch(1), 20) is called
0ms         | setTimeout(ajaxFetch(2), 40) is called
0ms         | setTimeout(ajaxFetch(3), 60) is called
20ms        | ajaxFetch(1) is called
40ms        | ajaxFetch(2) is called
60ms        | ajaxFetch(3) is called

Which can be implemented with a slight change to your code

function uploadSteps(maxStep) {
   for (let x = 1; x <= maxStep; x++){
       setTimeout(function() {
           ajaxFetch(x)
       }, 20 * x); // change delay from 20 -> 20 * x
   }
} 

Also it looks like you don't need to return anything from ajaxFetch(), so it's better to make it async so it does not block the code execution:

function ajaxFetch(s) {
    $.ajax({
        type: "POST",
        url: "post.php",
        data: {
            step: s
        },
        // async: false, -- remove this, it's true by default
        dataType: 'JSON',
        success: function (response) {
            $("#loadText").text(response.stepText);
            console.log(response.stepText);
        }
    });
}

Even if you actually do need to return something for fetchAjax(), it's better to keep it async and use callbacks/promises. jQuery actually strongly discourages using async: false in any case.


If the reason you added setTimeouts is to make sure all the steps are executed in order, then it's not the right way to do that. The problems are:

  • Let's say it took 100ms for the server to respond to the first request, and 10ms for the second one. Even with the 20ms delay the second request will be executed first. And just increasing the delay is not the solution, because:
  • If your server responds much faster the delay, you are introducing an unnecessary wait for the user.

It's better to add a callback from ajaxFetch() that will be called when ajax fetching is done, and then you'd call the next ajaxFetch() after you receive the callback.

Minderov
  • 521
  • 1
  • 5
  • 20
  • 1
    The setTimeout call is wrong btw, you're calling the function rather than passing it as a parameter. – Musa Jun 06 '18 at 18:32