0

I'm having trouble loading a text file into a string in a front end javascript application without any external libraries.

xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", filename, true)
xmlhttp.send()

xmlhttp.onreadystatechange = function() {
    if (xmlhttp.readyState == 4)
        myvariable = xmlhttp.responseText
}

This code loads the text file into a variable asyncronously, but the variable is only avaliable inside the onreadystatechange block when the response is eventually returned. Any attempt to get this variable outside this scope results in the code being executed before the response is returned and the variable is always undefined.

function getFile(filename, callback) {

    xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", filename, true)
    xmlhttp.send()

    xmlhttp.onreadystatechange = function() {
         if (xmlhttp.readyState == 4)
            callback(xmlhttp.responseText)
    }
 }

function loadFile(filename) {
    var myString
    getFile(filename, function(reponseText){
        myString = responseText
    })
    return myString
}

Some people have proposed this pattern as a solution to the problem. However this presents exacly the same problem, what is acctualy solving the problem in most scenarios where this is proposed is

async: false

I'm tring to abstract away the file loading process as it will happen multiple times in my code and possibly be revised several times. I want the entire file loading to be abstracted into a single funciton that I can call anywhere in my code without having to worry about nesting my code inside the callbacks of algorithmically unimportant IO tasks. I'm also trying to avoid just using a sycronous request. But having read other solutions, I'm not sure if this is possible.

I'm trying to do this in pure javascript without any external libraries.

EDIT 1/5/18

Having tried implementing this with async/await and Promises I'm left with a very similar situation.

function getFile(filename) {
    return new Promise(function(resolve, reject){   

        xmlhttp = new XMLHttpRequest()
        xmlhttp.open("GET", filename, true)
        xmlhttp.overrideMimeType("text/plain")
        xmlhttp.send()

        xmlhttp.onload = function() {
            if (xmlhttp.status == 200) {
                resolve(xmlhttp.responseText)
            }
            else {
                reject()
            }
        }
    })
}

async function loadFile(filename) {
    myfile = await getFile(filename)
    return myfile
}

Calling the loadFile() function returns a promise that resolves to the value of myfile. However to get that value we need to call

loadFile().then(myfile => { /* Use my varible here */})

This leaves me in exactly the same situation where the rest of my code needs to be wrapped in functions created by unimportant IO calls. The strange thing about this is that inside the loadFile() function that is declared async, if i use the the await keyword the code behaves syncronously, which is the behavior im trying to get in the rest of my code.

async function loadFile(filename) {
    myfile = await getFile(filename)
    /* myfile is set here, like in syncronous code*/
    return myfile
    /* This returns a promise, getting us back to square 1*/
}

The async/await syntax doesnt appear to offer any advantage over using a straight up promise, I've had to manually construct a promise anyway and I appear to be unpacking and repackaging the resulting value straight into another promise.

Am I missing something here?

  • 1
    Just get rid of `loadFile()` and always call `getFile()`. Or more robust modern approach instead of callbacks is use promises. See [How do I return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – charlietfl Apr 29 '18 at 12:55
  • There's pretty much no getting around the fundamentally asynchronous nature of JavaScript and browser APIs. – Pointy Apr 29 '18 at 12:57
  • This is a case where a small request library like superagent or axios will save you considerable development time. Also look into using newer [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) API if you don't need to support legacy browsers – charlietfl Apr 29 '18 at 13:01
  • If you consider possible, take a look at *await*/*async* and/or *Promise*s. Otherwise, the callback mechanism is a common solution because a file loader is asynchronous and I think that you cannot write (clean) synchronous code that works asynchronously. – xdrm Apr 29 '18 at 13:01
  • Having looked at await/async and Promises it seems that an async function always returns another promise (even when it has no return value?). "Awaiting" the result of a promise from the async function provides the value of the resolved promise inside that function but then its impossible to return that value directly from the async function? – Ben Hemsworth May 01 '18 at 13:42

1 Answers1

0

You can pass object to the loadFile function and capture the fileResponse like shown below:

    var fileObject = {
      fileResp: ''
    }

    function loadFile(filename, fileObject) {
        getFile(filename, function(reponseText){
            fileObject.fileResp = responseText
            triggerFileComplete()
        })
        return fileObject;
    }

   function triggerFileComplete() {
    // Do your next operation
    }

Maybe later or inside triggerFileComplete you can use the fileObject.fileResp.

Karthik S
  • 313
  • 2
  • 7