简体   繁体   中英

How to assign http response to global constant in javascript?

I've been trying to write some client-side javscript which does the following things asynchronously:

  1. Request a text file from the server and pass the response to a callback
  2. Use said callback function to parse the response into an array
  3. Assign that array to a constant in the global scope

I want to assign the response data to a constant in the global scope because it will allow me to access the array by clicking on the UI without sending the same HTTP request on every click. It's been a few days now and I'm starting to think that step 3 is impossible. Here is the function I'm using to send an http request and handle the response.

"use strict"
// getText will send an HTTP GET request to a given URL, and then
// pass the response text to a callback function so it can be parsed.
function getText(url, callBack) {
    const rawFile = new XMLHttpRequest()
    rawFile.open("GET", url)
    rawFile.onreadystatechange = ()=>{
            if (rawFile.readyState === 4) {
                   if (rawFile.status === 200 || rawFile.status === 0) {
                           callBack(rawFile.responseText)
                   }
            }
    }
    rawFile.send(null)
}

And here is the part of the code where the text is parsed, and then assigned to a global variable. It does work, but it has a problem:

let wordBank
function textParserCallback(text) {
    wordBank = text
            .split("\r\n")
            .filter((string)=>{
                    if (string.length > 0) return string
            })
}
getText("textFile.txt", textParserCallback)

Any function can assign values to wordBank, despite the fact that wordBank will never need to be changed or updated. This is bad design and will most certainly lead to bugs. I attempted to solve the problem by placing the response handler logic into a revealing module:

function myModule() {
    let wordBank
    function textParserCallback(text) {
        wordBank = text
                .split("\r\n")
                .filter((string)=>{
                        if (string.length > 0) return string
                })
    }
    getText("textFile.txt", textParserCallback)

    // the return statement will fire before textParserCallback is finished
    return wordBank
}
const globalWordBank = myModule() // undefined

But the return statement fires too early.

I've read How do I return the response from an asynchronous call? and various other questions which were solved by simply handling the response directly after the corresponding request, however as I stated above, I do not want to keep requesting the same, immutable data every time I need to update the UI. Another issue with that approach is that putting all of my text parsing, DOM listening, and UI updating code into one long callback makes my code really hard to read and debug. "getText" would no longer adequately describe what was going on.

If this does turn out to be impossible, I can always just compile the text file into an array and copy paste that into my program. Still, part of me worries that one day, I'll encounter this problem again, in a different form, and on that day the data will be too large to store in the program. With that in mind, I'm posting this here, hoping to find a solution, or proof that what I am attempting is indeed impossible.

The data obviously won't be available until the asynchronous request completes (you clearly know that). If you want to create a global constant at that point, you can do so with Object.defineProperty :

Object.defineProperty(window, "globalWordBank", {
    value: theValueGoesHere
});

That will be non-writable and non-configurable (and non-enumerable) by default.

Of course, this means you need to hold up any code that may use it until the request has completed (as part of some kind of bootstrap sequence), or code has to bend over backward to check whether it's been created yet:

if (typeof globalWordBank !== "undefined") {
    // It's okay to use it
} else {
    // Um...it's not there yet...hmmm...
}

That kind of bootstrap process isn't unusual, though.

If you don't want to use a bootstrap process and want other code to be able to run and not worry about whether the word bank is there yet, then I'd use a promise and make the global be the promise:

const globalWordBank = new Promise((resolve, reject) => {
    // ...ajax code to get the data and either resolve or reject
});

or even

const globalWordBank = fetch(/*...*/)
    .then(res => res.text())
    .then(text => text.split("\r\n").filter(string => string.length));

...since fetch provides a promise. (Note I've cleaned up the filter bit for you, no need for the if , and no need to return string; filter expects a flag from the callback, not the value.)

Two advantages:

  1. Promises can only be settled once , and only by code that has access to the promise's resolve or reject , so you don't have to worry about it changing.

  2. All uses of it are the same:

     globalWordBank.then(data => { // ...use the data... }); 

    Eg, since it may not be available when code wants it, always handle the fact by using the promise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM