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')