var str;
// [1] Since event handling is asynchronous in JS, the event handler
// function 'handleReadyStateChange' will execute and return a result
// at an unpredicted time sometime in the future.
// Since it is asynchronous, it will not block subsequent code [2]
// but instead pass the execution step to that line.
xhr.addEventListener("readystatechange", function handleReadyStateChange() {
if (this.readyState === this.DONE) {
str = this.responseText;
}
});
// [2] This line will execute immediately after [1] while [1] is STILL
// at work and has not yet finished its operations.
document.getElementById("result").innerHTML = str;
I think it helps to see your code like this:
var str; // At this point, the value of str is 'undefined'
// This line will ALWAYS execute first, and thus pass the value
// 'undefined' to innerHTML.
// That's why I've placed this line before the xhr code.
document.getElementById("result").innerHTML = str;
// At some point, the callback function will execute and
// pass the responseText to str subsequently to innerHTML
xhr.addEventListener("readystatechange", function handleReadyStateChange() {
if (this.readyState === this.DONE) {
str = this.responseText;
document.getElementById("result").innerHTML = str;
}
});
Bottom line is, that in asynchronous operations (like event handling, fetch() operations, setTimeout, etc.),
your only option is to place the code that depends on the result of the asynchronous operation, inside a callback
function (or a then() in the case of Promise-based asynchronous commands).
This is just a short intro to a great chapter in JavaScript called asynchronous programming. I suggest you rethink about your code again and then head over to the article pointed by @Bravo: How do I return the response from an asynchronous call?
And, speaking of chapters, here's a great one that dives into Asynchronous Programming in full detail: https://eloquentjavascript.net/11_async.html