2

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>
Community
  • 1
  • 1
F.S.
  • 1,175
  • 2
  • 14
  • 34
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Marty Aug 04 '16 at 01:12
  • 2
    you're declaring `allText` twice (that makes two variables with same name but diferent scope), remove the second `var` keyword before your variable. Good luck with JavaScript! – Santiago Hernández Aug 04 '16 at 01:13
  • @SantiagoHernández That's part of it, but it still wouldn't solve the asynchronous fulfillment race condition, and it would still be undefined until the XHR request completes. – Brandon Anzaldi Aug 04 '16 at 01:16
  • Dang... I spent 2+ hours on this thing. Thank you @SantiagoHernández – F.S. Aug 04 '16 at 01:18
  • 1
    @BrandonAnzaldi although I don't fully understand your concern, this script works now – F.S. Aug 04 '16 at 01:19
  • Glad to hear it worked! However, on non-local files, or if there's latency in reading local files, the function will return `allText` before it's been defined by `onreadystatechanged`. Since it's local, it may happen more or less instantaneously, and not cause a problem. :) EDIT: Didn't notice the XHR request was in Synchronous mode. :P – Brandon Anzaldi Aug 04 '16 at 01:21
  • 2
    chris not problem!, @BrandonAnzaldi yeah I know it's a bad practice to do sync Ajax requests, but he's learning the basics I'm assuming – Santiago Hernández Aug 04 '16 at 01:22
  • 1
    @SantiagoHernández Ahhh, I didn't even notice that the XHR was synchronous initially. I was assuming the problem stemmed from the callback returning out-of-step with the rest of the script. The variable redeclaration was the correct answer to solve the immediate problem! And the async control flow of JS can be a pain to wrap your head around regardless of skill level. It's super easy to wind up in [callback hell](http://callbackhell.com/). :) – Brandon Anzaldi Aug 04 '16 at 01:26
  • 1
    @SantiagoHernández I've also seen comments about using sync AJAX is depreciated. Just curious, what's the "correct" solution? – F.S. Aug 04 '16 at 01:48
  • 2
    a better solution is to do it @BrandonAnzaldi's way – Santiago Hernández Aug 04 '16 at 01:49
  • 1
    @SantiagoHernández I guess after changing "false" to "true"? – F.S. Aug 04 '16 at 01:50
  • 1
    @Chris That should be all that you need to do. Or you could remove the third argument entirely since it defaults to `true`. – Brandon Anzaldi Aug 04 '16 at 01:51
  • cool thanks. I'll mark @BrandonAnzaldi 's answer as accepted after he corrects it – F.S. Aug 04 '16 at 01:54
  • 2
    Should be correct now @Chris. :) Hope I was able to help. – Brandon Anzaldi Aug 04 '16 at 02:21

1 Answers1

3

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.

Community
  • 1
  • 1
Brandon Anzaldi
  • 6,884
  • 3
  • 36
  • 55
  • 2
    actually `allText` is being set syncronously, blocking the main thread... and it's completely accesible after `rawFile.onreadystatechange` finishes – Santiago Hernández Aug 04 '16 at 01:26
  • 1
    Yup! My bad. I didn't notice the `false` flag for async on the XHR request initially. Credit where credit is due, you had the correct answer :) – Brandon Anzaldi Aug 04 '16 at 01:28
  • 2
    no problem :).. btw the callback hell is truly a hell – Santiago Hernández Aug 04 '16 at 01:31
  • 1
    @both of you, sorry for this dumb question I just posted. As Santiago mentioned, it's really just a syntax issue. I was overwhelmed by all the jargon during the debugging. Also, is there a javascript IDE to help me avoid this kind of mistake? – F.S. Aug 04 '16 at 01:33
  • 1
    @Chris It's not a dumb question at all! I didn't even notice the core problem initially. A linter should help you avoid these issues. Whatever editor you're using, there's probably a linter plugin for it :) ES-Lint or JSHint are the two linters that come to mind, and will give you stylistic and syntax hints, plus helping to catch issues you might have otherwise missed. :) – Brandon Anzaldi Aug 04 '16 at 01:35
  • 1
    @BrandonAnzaldi I use notepad++. awesome I'll try it out. Thanks! – F.S. Aug 04 '16 at 01:39
  • 1
    @Chris http://stackoverflow.com/questions/1046810/using-jslint-in-notepad No worries. Happy coding! – Brandon Anzaldi Aug 04 '16 at 01:40