2

I have a function that prints out text from an element, followed by text from an element that is requested from a different webpage.

function together(){
var finalString ="";

 var x = document.getElementsByClassName("h-userinput");   
    for (var i = 0; i < x.length; i++) {
var text = x[i].innerText || x[i].textContent;
    console.log(text+"\n");


        var query = "#ctl00_ContentPlaceHolder_ResultsGrid_TB > tr:nth-child(bbcc) > td.total-score-column";
            query = query.replace("bbcc",i+1);

        var poop = ((document.querySelector(query)).onclick.toString());    
        text = poop.replace("javascript:location.href=\'","");
        text = text.replace("function onclick(event) {","");
        text=text.replace("}","");
        text = text.replace("\';","");
        text = "https://bartholomew.itslearning.com"+text;

        var url = text;
        fetch(url)
        .then(res => res.text())
         .then((responseText) => {
          const doc = new DOMParser().parseFromString(responseText, 'text/html');
        console.log(doc.querySelector("#qti-choiceinteraction-container > tbody > tr.checkedrow > td.answer > div > label > span > p")+"\n");

         })
         .catch((err) => {
          // There was an error, handle it here
         });



    }



//console.log(finalString);


}

The problem is that when I run this, it takes a moment to get a reply from the other webpage, but the loop continues to execute without getting the reply and printing out the result. I would like to wait until the element is printed out before moving on to the next iteration of the loop.

  • Does this answer your question? [Wait promise inside for loop](https://stackoverflow.com/questions/48014050/wait-promise-inside-for-loop) – Matt U Dec 15 '19 at 04:31

2 Answers2

0

The issue here is the combination of synchronous code (ie the for loop) and asynchronous code (the fetch() network request), which is resulting in the undesired behavior you noted. The solution is to block (prevent) subsequent iteration of the loop until the the asynchronous network call in the current iteration has completed.

There are a number of ways to achieve this - one Promise based solution is to use Promise.all() to evaluate multiple promises (ie for each loop iteration), to ensure that all iterations are performed in lock step with each other. See the modified copy of your code below with comments detailing how this can be done:

function together() {
  var finalString = "";

  // Define local helper function that performs iteration logic. We've
  // basically moved the logic from for loop into this "iteration 
  // function". A key point to note is that function returns the
  // promise generated by fetch()
  function asyncIteration(hUserInput) {

    var text = hUserInput.innerText || hUserInput.textContent;
    console.log(text + "\n");

    var query = "#ctl00_ContentPlaceHolder_ResultsGrid_TB > tr:nth-child(bbcc) > td.total-score-column";
    query = query.replace("bbcc", i + 1);

    var poop = ((document.querySelector(query)).onclick.toString());
    text = poop.replace("javascript:location.href=\'", "");
    text = text.replace("function onclick(event) {", "");
    text = text.replace("}", "");
    text = text.replace("\';", "");
    text = "https://bartholomew.itslearning.com" + text;

    var url = text;
    return fetch(url) // Important to "return" this promise, which has
                      // the effect of "blocking" iteration until the
                      // fetch responds
      .then(res => res.text())
      .then((responseText) => {
        const doc = new DOMParser().parseFromString(responseText, 'text/html');
        console.log(doc.querySelector("#qti-choiceinteraction-container > tbody > tr.checkedrow > td.answer > div > label > span > p") + "\n");

        // Combine all response text into final string to illustrate
        // iteration of each async request in lock step
        finalString += responseText + " ";

      })
      .catch((err) => {
        // There was an error, handle it here
      });
  }

  // Obtain an array of all elements with class h-userinput
  var hUserInputs = Array.from(document.getElementsByClassName("h-userinput"));

  // Map each element to a promise by function above. This has 
  // the effect of performing the fetch for each element 
  // asynchronously. When each per-iteration fetch has completed, the
  // message below will be logged to console
  Promise.all(hUserInputs.map(asyncIteration)).then(() => {
    console.log("All iterations complete", finalString);
  });

}

Hope that helps!

Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
0

You can use async and await to make it more simpler. Just add async to your function and handle fetch using await.

async function together(){
    var finalString ="";

     var x = document.getElementsByClassName("h-userinput");   
    for (var i = 0; i < x.length; i++) {
    var text = x[i].innerText || x[i].textContent;
        console.log(text+"\n");


            var query = "#ctl00_ContentPlaceHolder_ResultsGrid_TB > tr:nth-child(bbcc) > td.total-score-column";
                query = query.replace("bbcc",i+1);

            var poop = ((document.querySelector(query)).onclick.toString());    
            text = poop.replace("javascript:location.href=\'","");
            text = text.replace("function onclick(event) {","");
            text=text.replace("}","");
            text = text.replace("\';","");
            text = "https://bartholomew.itslearning.com"+text;

            var url = text;
            try{
            let res = await fetch(url)
            let responseText = res.text()
            const doc = new DOMParser().parseFromString(responseText, 'text/html');
            console.log(doc.querySelector("#qti-choiceinteraction-container > tbody > tr.checkedrow > td.answer > div > label > span > p")+"\n");   

            }
            catch{ console.log('Error')}


        }    
    //console.log(finalString);


    }
RK.
  • 3
  • 3