JavaScript AJAX – How to Open Local Files in JavaScript

ajaxjavascript

I'm new to JavaScript. I found an example to open local files with javascript on StackOverflow. After some googling, I'm able to set my Chrome to allow reading local files, and I am then able to run that example. However, I want to return the string allText and use it later in my script. But the string become undefined outside readTextFile().

There is a similar question here. It seems like it has something to do with the asynchronous feature of AJAX. I can barely the understand the jargons at the moment. I just don't see why in this post the third parameter of XMLHttpRequest.open() is set to be true.

Anyway, below is my current code. I want to use allText outside function readTextFile().

<!DOCTYPE html>
<html>
    <script>
        function readTextFile(file)
        {   
            var allText;
            var rawFile = new XMLHttpRequest();
            rawFile.open("GET", file, false);
            rawFile.onreadystatechange = function ()
            {
                if(rawFile.readyState === 4)
                {
                    if(rawFile.status === 200 || rawFile.status == 0)
                    {
                        var allText = rawFile.responseText;
                        alert(allText);
                    }
                }
            }
            rawFile.send(null);
            return allText; // this is the part that goes wrong I think
        }

        t = readTextFile("foo.file");
        document.write(t) // print out "undeifned" instead of the correct answer

    </script>
</html>

Best Answer

This is actually most likely a scope issue. Because you're setting allText asynchronously, it's not available immediately after the function returns. In addition, you're reinitializing allText within a function, which messes with the scope of the return regardless.

rawFile.onreadystatechange is executed after the function returns. You can either move the execution into the XHR callback, or wrap the function in a promise, which would still require you modify your control flow a bit.

Move the document.write:

<!DOCTYPE html>
<html>
    <script>
        function readTextFile(file)
        {   
            var allText;
            var rawFile = new XMLHttpRequest();
            rawFile.open("GET", file);
            rawFile.onreadystatechange = function ()
            {
                if(rawFile.readyState === 4)
                {
                    if(rawFile.status === 200 || rawFile.status == 0)
                    {
                        allText = rawFile.responseText;
                        document.write(allText);
                    }
                }
            }
            rawFile.send(null);
        }

        readTextFile("foo.file");

    </script>
</html>

Promisified:

function readTextFile( file ) {
  return new Promise( function ( fulfill, reject ) {

    var allText;
    var rawFile = new XMLHttpRequest();
    rawFile.open( "GET", file );
    rawFile.onreadystatechange = function () {
      if ( rawFile.readyState === 4 ) {
        if ( rawFile.status === 200 || rawFile.status == 0 ) {
          fulfill( rawFile.responseText )
        }
      }
    }
    rawFile.send( null );
  } );
}
readTextFile( "foo.file" )
  .then( function ( t ) {
    document.write( t );
  } );

Both of these will ensure that your script doesn't attempt to use allText until it's been returned by the XHR request.

Though as Santiago Hernández pointed out, the XHR request is synchronous, and the scope issue was of a different nature than I first assumed. The problem lies in redeclaring the variable within the function, resulting in the one returned to be undefined.

Related Question