Search code examples
javascriptjqueryajaxcurlnested-loops

Optimizing loop of nested ajax calls


Disclaimer: final result works, I just don't understand the order in which a loop of nested ajax calls works.

Basically I have two ajax calls which retrieve data from an external API through curl GET requests. They both require parameters from the previous call to proceed. Each one of this "nested loop" is called in a $().each loop like so:

simple structure of calls:

$().each{
getData() --calls--> ajaxCurl1 --success--> ajaxCurl2 --success--> showData()
}

bit more detailed structure (had to omit some code)

$("#updateStats").on("click", function(){
        $(".stats-card").each(getStats);
});


function getStats() {
    showChart = false;  //global variable used in the last ajax nested call
    ajaxCall1(this.id);
}


function ajaxCall1(id) {
    console.log("ajaxCall1");

    $.ajax({
        type: "POST",
        url: "backend.php",
        dataType: "json",
        data: {},

        success: function (result) {
            
      let response = JSON.parse(result.response);
      let dataLink = response.link ?? null;
      ajaxCall2(dataLink, id);

        }
    
    });
}

function ajaxCall2(url, id) {
    console.log("ajaxCall2");

    $.ajax({
        type: "POST",
        url: "backend.php",
        dataType: "json",
        data: {},

        success: function (result) {
            
      let response = JSON.parse(result.response);
      let data = response.data ?? null;
      showData(data, id);

        },
    });
}


function showData(jsonData, id) {
//do stuff
    if (showChart === true) {
  // show chart
  }
}

I expected the calls to go one by one, doing the call and updating the Data in blocks like this:

//element 1
getStats();
ajaxCall1();
ajaxCall2();
showData(); //DOM UPDATE
// element 2
getStats();
ajaxCall1();
ajaxCall2();
showData(); //DOM UPDATE
....
// element n

but instead what I see in the console print is:

getStats(); // n times
ajaxCall1(); // n times
ajaxCall2(); // n times
showData(); // DOM UPDATE n times

Solution

  • Javascript is asynchronous by default (this video is really good). In a nutshell, when it gets to an $.ajax it will let it running in the background and go to next line it's almost running everything at once. To prevent that you need to use async/await:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
        <title>Document</title>
        <script>
          $(function () {
            $("#updateStats").on("click", async function () {
              const cardsArray = $(".stats-card").toArray();
    
              for (let i = 0; i < cardsArray.length; i++) {
                const card = cardsArray[i];
                const result1 = await ajaxCall1(card.id);
                const result2 = await ajaxCall2(result1.id, card.id);
                showData(card.id, result2.id);
                console.log("-");
              }
            });
          });
    
          async function ajaxCall1(id) {
            console.log("ajaxCall1");
    
            return await $.ajax({
              type: "POST",
              url: "https://jsonplaceholder.typicode.com/users",
              dataType: "json",
              data: {},
            });
          }
    
          async function ajaxCall2(url, id) {
            console.log("ajaxCall2");
    
            return await $.ajax({
              type: "POST",
              url: "https://jsonplaceholder.typicode.com/todos",
              dataType: "json",
              data: {},
            });
          }
    
          function showData(id, someData) {
            console.log("Show data");
            $(`#${id}`)[0].innerHTML += " UPDATED " + someData;
          }
        </script>
      </head>
      <body>
        <button id="updateStats">Update stats</button>
        <p id="card1" class="stats-card">Card 1</p>
        <p id="card2" class="stats-card">Card 2</p>
        <p id="card3" class="stats-card">Card 3</p>
        <p id="card4" class="stats-card">Card 4</p>
        <p id="card5" class="stats-card">Card 5</p>
        <p id="card6" class="stats-card">Card 6</p>
        <p id="card7" class="stats-card">Card 7</p>
        <p id="card8" class="stats-card">Card 8</p>
        <p id="card9" class="stats-card">Card 9</p>
        <p id="card10" class="stats-card">Card 10</p>
      </body>
    </html>