1

In the below example from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/onreadystatechange, I am trying to store the responseText in a variable that can be accessed outside of the onreadystatechange function.

const xhr = new XMLHttpRequest(),
    method = "GET",
    url = "https://developer.mozilla.org/";

xhr.open(method, url, true);
xhr.onreadystatechange = function () {
  // In local files, status is 0 upon success in Mozilla Firefox
  if(xhr.readyState === XMLHttpRequest.DONE) {
    var status = xhr.status;
    if (status === 0 || (status >= 200 && status < 400)) {
      // The request has been completed successfully
      console.log(xhr.responseText);
    } else {
      // Oh no! There has been an error with the request!
    }
  }
};
xhr.send();

At the moment, the only way I know to handle the return value from a function is to do the following

const variable = function()

But I can't figure out how to make that won't work due to the way the onreadystatechange event handler is initiated/called. I tried declaring a variable outside of the function scope, but it keeps returning 'undefined'.

Thanks for your help.

*Edit for async/await with fetch I've been trying to implement the async/await fetch option but can't seem to get it to work right. I've been checking the output with alerts and the alert located outside the function always fires before the one inside the function. As a result the one outside is giving me "object Promise" and the one inside is the actual data. I'm returning the data but it's just not working.

  const getData = async function (filePath) {
    let myData
    await fetch(filePath)
      .then(response => response.text())
      .then(data => myData = data)
    alert(myData)
    return (myData)
  }

  let theData = getData('./2021WeatherData.csv')
  alert(theData)

So "alert(theData)" fires before "alert (myData)" and I can't understand why. Thanks so much.

SunnyNonsense
  • 410
  • 3
  • 22

1 Answers1

0

This is a good question.

The XMLHttpRequest is code that runs asynchronous. And it can't run synchronously as the yellow warning on the Mozilla developer documentation explains.

So what does that mean?

(Pseudocode)

var result
console.log('start')
xhr.onreadystatechange = function() {
   result = '5'
   console.log('request done')
}
console.log('result', result)

This will output as you already noticed

start
result undefined
request done

and you are expecting:

start
request done
result 5

But this does not work, because the console.log('result', result) was already executed before the variable was assigned.

Solutions

Using a callback

Whatever you want to do with the result, you need to do within the callback function. Like:

  • Console.log the value
  • Updating the browser dom (I added this as an example)
  • Store to the database on a node server

The callback function is the function you are assigning to the onreadystatechange.

(Pseudocode)

xhr.onreadystatechange = function() {
   // This code is the callback function. 
   // You can do everything with the result, like updating the DOM,
   // but you can't assign it to a variable outside.
}

Actual code

function logResultToTextBox(result) {
  document.getElementById('#server-result-text-box').value = result
}

const xhr = new XMLHttpRequest(),
    method = "GET",
    url = "https://developer.mozilla.org/";

xhr.open(method, url, true);
xhr.onreadystatechange = function () {
  // In local files, status is 0 upon success in Mozilla Firefox
  if(xhr.readyState === XMLHttpRequest.DONE) {
    var status = xhr.status;
    if (status === 0 || (status >= 200 && status < 400)) {
      // The request has been completed successfully
      console.log(xhr.responseText);
      // DO WHATEVER YOU NEED TO DO HERE WITH THE RESULT, BUT YOU CANT ASSIGN THE VALUE TO VARIABLE WHICH IS DECLARED OUTSIDE
      logResultToTextBox(xhr.responseText);
    } else {
      // Oh no! There has been an error with the request!
    }
  }
};
xhr.send();

Wrap it using a promise

The promise is another way on how you can solve this. You wrap it into a promise function. Not that the promise has no return statement. It uses a resolver. The actual work will then be done after you define the functions. It will be called in the then block. Here you also can't assign a value to a variable that is declared outside.

Pseudocode:

Promise makeRequest(url) {
  // doRequest to url and resolve result
}

makeRequest('http://example.com).then((result) => {
  // do whatever you want with the result
})

Actual code:


function logResultToTextBox(result) {
  document.getElementById('#server-result-text-box').value = result
}

function makeRequest (method, url) {
  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.onload = function () {
      if (this.status >= 200 && this.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: this.status,
          statusText: xhr.statusText
        });
      }
    };
    xhr.onerror = function () {
      reject({
        status: this.status,
        statusText: xhr.statusText
      });
    };
    xhr.send();
  });
}

// Example:

makeRequest('GET', 'https://developer.mozilla.org/')
.then(function (result) {
  console.log(result);
  logResultToTextBox(result)
})
.catch(function (err) {
  console.error('Augh, there was an error!', err.statusText);
});

Use async / await

For a few years, there is also a new syntax called async/await. This gives you the possibility to write code like it would a normal code.

Pseudocode

let myServerResult;
async function doAsyncCall(url) {
    const serverResult = await callServer(url);
    return serverResult
}

myServerResult = await doAsyncCall('http://example.com') 
console.log(myServerResult)

In that case, I would use fetch instead of XMLHttpRequest because it is much simpler.

Actual code:

const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const jsonResult = await response.json(); 
console.log(jsonResult)

(note that I am using jsonplaceholder.typicode.com as an API as the HTML output of the Mozilla documentation does not work with it. If you really need XMLHttpRequest then have a look into the answers on this question)

P. S. Don't use old synchronous code syntax

There is also an old-school way by using "Synchronous request". But this should not be used anymore, as it leads to a bad user experience because it makes the loading of the website slow. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests#synchronous_request

Answer after the question edit

You need to await the following line

instead of

  let theData = getData('./2021WeatherData.csv')

you need to write


  let theData = await getData('./2021WeatherData.csv')

basti500
  • 600
  • 4
  • 20
  • Thanks for the super thoughtful response. I've been trying to implement the async/await fetch option but can't seem to get it to work right. I'm going to throw some additional code in the original post. – SunnyNonsense Sep 05 '21 at 19:58
  • I'm a bit late getting back to this, but I threw "await" in front of the function call and I get the error "...Unexpected reserved word 'await'." It seems like this is invalid syntax. Thanks for the suggestion though. – SunnyNonsense Sep 27 '21 at 01:03
  • Are you running on server or on browser? – basti500 Sep 30 '21 at 14:38