Using GET to download the contents of a text file to be put into a variable and used later on outside of XMLHttpRequest scope?

In the following example, why does the “test” variable contain the response data when being read within the processRequest() function but not when being displayed in console.log() further down the code and outside of the XMLHttpRequest context?

		var xhr = new XMLHttpRequest(),
		    test = '';//Empty string variable intended for the XMLHttpRequest response data...

		function processRequest(){
			if (xhr.readyState == 4){
				test = this.responseText;
				console.log(test);//Developer Tools shows what I expect.
			}
		}

		xhr.responseType = 'text';
		xhr.open('GET', 'temp');//The file is literally named "temp" and is in the same directory. (It's just a test file containing Lorem Ipsum test data.)
		xhr.send();
		xhr.onreadystatechange = processRequest;

		console.log(test);//Developer Tools shows "undefined"... Why?

It’s my understanding that this is likely caused by the asynchronous nature of XMLHttpRequest. If so, how do I use the “test” variable later on after XMLHttpRequest is completed?

Insight is appreciated. I’ve tried to use promises and callbacks… Nothing ever seems to work.

It’s because asynchronous activites don’t occur right away. That’s why things relating to the updated test value must be done from the processRequest function.

Hi,

Paul has already given you the answer as to why this doesn’t work as you expect, but for a more in-depth look at the problem and potential solutions, you should read this StackOverflow thread.

There are also a couple of things that could be done to improve the code you posted. I would consider using the more modern Fetch API and reading about flow control in modern JavaScript (Promises and async…await). We have a nice article on that:

Once you have understood what is happening and why, I would stick the file fetching functionality in its own function and call it like so:

async function getFileContents(fileName) {
  const response = await fetch(fileName);
  if (!response.ok) throw new Error(response.status);
  const contents = await response.text();

  return contents;
}

getFileContents('temp')
  .then(res => {
    console.log(res);
  })
  .catch(error => console.error(error));
1 Like

Okay, taking the following as an example then, I would expect the contents to be undefined whenever I try to access it outside of the aynch context and yet, I can see it in the “test” array / object but not when I try to access it directly via something like “console.log(test[0]);”…

		var test = [];

		async function getFileContents(fileName) {
			const response = await fetch(fileName);
			//if (!response.ok) throw new Error(response.status);
			const contents = await response.text();
			return contents;
		}

		getFileContents('temp').then(res => {
			//console.log(res);
			test.push(res);
		}).catch(error => console.error(error));

		console.log(test);//I can see the file contents in the array...
		console.log(test[0]);//But this shows undefined?

 

Considering the async nature, I would expect the contents to be undefined across the board, but yet, I can see it at the 0 index of the test array / object and yet, it comes up as undefined if directly accessed. That seems inconsistent to me. Is that just a browser bug or inconsistency? And out of curiosity, is there a simple way to perform a JavaScript GET request for the text file contents to be later used in a variable without being forced into this asynchronous stuff? Because this seems to force one to shove everything into a callback world for everything that depends on the response data.

I’m not surprised that this is not working as you desire, but as to the exact reason that you are seeing the results you are seeing, I am not sure…

My best guess is that the array is “reactive”. It is logged as an empty array on the console, but by the time you open it to view it (it is initially folded up), it has been populated with the output.

You can actually test this with the following code:

const dummy = [];
setTimeout(() => { dummy.push("hi"); }, 5000)
console.log(dummy);

Run this code with the console open, then open the array (i.e. unfold it to inspect its values) immediately. It will be empty and will stay empty past five seconds.

Then, reload the page and wait for longer than five seconds before opening up the array. This time it will be populated.

It seems to me that the browser isn’t checking the contents of the array until you open it up.

You are performing an Ajax request to get the contents of the file. The ‘A’ in Ajax stands for Asynchronous, so if you attempt to make this synchronous, you’ll just end up fighting the language.

There are a few work arounds out there (see the SO link I posted previously), but I wouldn’t recommend this approach.