0

This is my first question and I hope I don't do anything wrong. First of all, thank you for reading.

And my problem is...
The design is to read some data in a text file with JavaScript, process them through a number of functions before creating the content to display in an HTML div. After some searching, I figured that it could be done with XMLHttpRequest. Because the read data will be processed by some functions, I decided to store them to a global variable for easy access. The code seemed to be working fine at first and I could print the obtained data to a div. But then I noticed a strange bug. If I assign those data to a global variable and attempt to retrieve them later, I will get the initially assigned value or undefined. I try to alert that global variable's value and I see what I get above. However, if I alert again, the value changes to what I needed. I have just been learning JavaScipt for a short while, facing this error completely leaves me at lost.

The html file:

<html>

<head>
    <meta charset="UTF-8">
    <title>Read file</title>

<script>
var output = ["next"];

function edit()
{
    var rawFile = new XMLHttpRequest();
    rawFile.open("GET", "test.txt", true);
    rawFile.responseType = "text";
    rawFile.onreadystatechange = function ()
    {
        if(rawFile.readyState === 4)
        {
            if(rawFile.status === 200 || rawFile.status == 0)
            {
                output[0] = rawFile.responseText;
                //alert("Reading okay!");
            }
        }
    };
    rawFile.send(null);
    console.log(output[0]); // initial value
    alert(output[0]); // initial value
    console.log(output[0]);  // desired value
    alert(output[0]);  // desired value
}
</script>
</head>

<body>
    <button onclick="edit()">Read test.txt</button>
</body>

</html>

The text file:

This is the content of the text file.

Temporarily, I have to alert every single time the text file is read which isn't a good idea to solve the problem.
My question is, with the above design, is there any better way to implement it without having to deal with this bug?
And here is the demo: html and text.

Thank you very much.

  • this is because you are trying to get the data right away, by default the xhr calls are async meaning it runs while your code continues, you need to either hook onreadystatechange or change the call to a sync one – Patrick Evans Apr 07 '14 at 16:42
  • 1
    See http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-ajax-call – Felix Kling Apr 07 '14 at 16:51
  • 1
    @Felix Kling: Thank you for the good reading. Its detail and comprehensiveness is a big help to me. I now understand more about synchronous and asynchronous and the workarounds. – user3507343 Apr 09 '14 at 12:51

2 Answers2

1

That's because the value changes asynchronously.

The alert is no guaranty, it's just a delay after which the AJAX callback could have been executed or not.

If you want to use the desired value, you must run your code in onreadystatechange.


Example:

function edit(callback)
{
    /* ... */
    rawFile.onreadystatechange = function () {
        if(rawFile.readyState === 4 && (rawFile.status === 200 || rawFile.status == 0)) {
            output[0] = rawFile.responseText;
            //alert("Reading okay!");
            callback();
        }
    };
    /* ... */
}
fuunction afterEdit(){
    alert(output[0]);  // desired value
}

<button onclick="edit(afterEdit)">Read test.txt</button>
Oriol
  • 274,082
  • 63
  • 437
  • 513
0

Since the AJAX call is asynchronous, it is being executed after your edit function returns... Since it sounds like you are passing your data through a series of functions, I suggest using a promise library (Q.js for instance). Here is a simple jsfiddle that demonstrates using Q.js.

Your AJAX call would simply resolve the promise, kicking off the chain of functions to execute. My example shows modifying the data at each step, but this is not necessary. The return value of the prior function will be used as the input for the next function. I've commented out the AJAX stuff and used setTimeout to mimic async call:

//Global variable for test.txt
var test;

function edit()
{
    /*
    var deferred = Q.defer();
    var rawFile = new XMLHttpRequest();
    rawFile.open("GET", "test.txt", true);
    rawFile.responseType = "text";
    rawFile.onreadystatechange = function ()
    {
        if(rawFile.readyState === 4)
        {
            if(rawFile.status === 200 || rawFile.status == 0)
            {
                //resolve promise with responseText;
                deferred.resolve(rawFile.responseText);
            }
        }
    };
    deferred.promise
        .then(processStep1)
        .then(processStep2)
        .then(processStep3);
    */

    //Imitating async call that will finish after 2 seconds
    var deferred;
    var promise;
    //if we haven't read the file yet, then make async call
    if (test === undefined) {
        deferred = Q.defer();

        setTimeout(function () {
            test = "This is the content of the text file."
            deferred.resolve(test);
        }, 2000);

        promise = deferred.promise;
    }
    //Else we've already read the file. 
    else {
        promise = Q(test);
    }

    //Start adding your functions to process text here:
    promise.then(processStep1)
        .then(processStep2)
        .then(processStep3);
}

function processStep1(data) {
    alert("Step 1: " + data);
    //adding some stuff onto data for example
    data = data + "... And more data.";
    return data;
}

function processStep2(data) {
    alert("Step 2: " + data);
    data = "Adding data to front. " + data;
    return data;
}

function processStep3(data) {
    alert("Step 3: " + data);
    return data;
}

Above, I also use a global variable (test) for the data retrieved from async call. I check this value when deciding if I need to make an async call to get the value, or use the value that was already populated from the original async call. Use whatever pattern most fits your needs.

I would also recommend a library for doing the async calls as your project might get messy fast by doing raw AJAX calls.

Patrick
  • 6,828
  • 3
  • 23
  • 31
  • Thank you very much. Your explanation and demonstration are much appreciated. I will study about the promise library you suggested to work out the solution fits my case most. Before I started, I thought JavaScript was simple unknowing that there would be jQuery and AJAX to learn about. The whole picture is huge. – user3507343 Apr 09 '14 at 12:52