0

Hello I have the following code:

function outer() {

    try {
        ScriptLoader.getScripts('proj4script',proj4Callback);
    }
    catch(e) {console.log(e);}

    function proj4Callback() {
        x = proj4(some geo coord calcs here);
    }

    // I need access to the values stored in x here
}

As you can see from the code I load the proj4 script into my enviroment using a Scriptloader. Unfortunately this script loader has no return value. One of the arguments to the scriptloader is a callback function - this function can take no parameters.

I am trying to access data that I calculate intoside the callback function in my outer function. Is there a way for me to do this that does not involve using a global variable outside the outer function?

edits:

@Trinot I am not sure what you mean by continuing the asynchronous call pattern, do you mean like this:

function main() {
    x = await new Promise(outer());
}

async function outer().then({

    await new Promise(ScriptLoader.getScripts.bind(ScriptLoader, 'proj4script'));
    let x = proj4(/*some geo coord calcs here*/);

    return x;
})
Lynx
  • 57
  • 5
  • short answer? no. Why the aversion to uninitialized variables? – Robbie Milejczak Jan 25 '18 at 19:56
  • Can x be declared in function outer? It would be scoped to the outer function block and not truly global. – R.Laney Jan 25 '18 at 20:00
  • The values to `x` will almost certainly not be stored when the code at "here" is executed, since `getScripts` is going to be asynchronous. You need to handle the "ready" event from the script being loaded in `proj4Callback`. Essentially, the "here" code needs to move into the scope of `proj4Callback`, not the other way around – Tibrogargan Jan 25 '18 at 20:12
  • How do you call `outer`? Does the calling code need anything from it? Does the calling code need to be sure the script in question has been loaded once it has executed `outer`? – trincot Jan 25 '18 at 20:17

2 Answers2

2

You need to embrace the asynchronous nature of the callback. So you should not try to code in a synchronous way. Put the code that needs x inside the callback, or call a function from within that callback that passes x to yet another function, ...etc. Whatever you do, you need to continue the execution from within that callback.

Now there is one other, modern way to tackle this: promisify the getScript method, and use async/await:

async function outer() {
    await new Promise(ScriptLoader.getScripts.bind(ScriptLoader, 'proj4script'));
    let x = proj4(/*some geo coord calcs here*/);
    console.log(x);
    return x;
}

This will interrupt the function execution until the getScripts method calls the callback, which in this case is the Promise resolver. Once that is called the execution picks up after the await statement, and can access the variables you need to calculate x.

Be aware though that the code that calls outer will continue to execute before the script is loaded. If you have also there code that depends on the loaded script, you must chain a then method to the outer call, or put that code also in an async / await construct.

Here is how that would look, assuming you return x; in the above function:

async function main() {
    let x = await outer();
    // rest of code that depends on the loaded script
}
main();
// Don't put anything here that needs the loaded script, as it will execute sooner. 

Or else:

outer().then(function (x) {
    // rest of code that depends on the loaded script
}
// Don't put anything here that needs the loaded script, as it will execute sooner. 

NB: You can wrap the await in a try block, like you had it, but you can instead bind a catch method call on the outer() call.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Doesn't that kind of defeat the purpose of using a script loader? Maybe `.then` would be more appropriate? – Tibrogargan Jan 25 '18 at 20:35
  • The code that calls `outer` can continue to execute, and the browser can update, all before the script is loaded (depending on the script loader's implementation). `await` is syntactic sugar for `then`. – trincot Jan 25 '18 at 20:37
  • Thanks I was definitely thinking about the problem in a synchronous way. To be clear I am not using a callback by choice here. I need access to proj4 in my code and the only method of bringing that into the platform I am working on is via a ScriptLoader, and therefore via a callback. So in my case, the variables that I calculate inside the callback using proj4, are then immediately returned from outer() and used in the function that called outer(). I will investigate promises and aysnc/then/wait as I havn't had to use those in my js code before. – Lynx Jan 25 '18 at 20:47
  • In that case you need to continue the asynchronous pattern at the calling side of `outer` and do `outer().then(function () { // rest of code })`, or do the same `async/await` trick there. – trincot Jan 25 '18 at 20:54
  • Your promise should resolve or reject, the one you wrote don't do that, error syntax. – Fernando Carvajal Jan 25 '18 at 20:55
  • @FernandoCarvajal, a Promise constructor callback can be written explicitely (with `(resolve, reject) => ...`) or can just be a function reference, which deals with that. I have done the latter, although `reject` is not called explicitly, but an exception in a promise constructor callback translates into a rejection, so that is OK. This is valid syntax. – trincot Jan 25 '18 at 20:56
  • Sure it can be a function reference, but you have to trigger the resolve, reject or whatever function you have pass... unless the callback execution that the async function have is the one triggers that. **Ugly way** – Fernando Carvajal Jan 25 '18 at 21:01
  • The `resolve` *is* triggered. It is the `setScript` callback argument. Nothing ugly about that. – trincot Jan 25 '18 at 21:03
  • Hi trincot sorry if you got a bunch of comment notifications, I was having issues with formatting. I updated my original post with a question. – Lynx Jan 25 '18 at 21:10
  • See how you need to call `outer` in piece I added to my answer. – trincot Jan 25 '18 at 21:15
  • Yes I see now. Thank you Trincot this makes sense. – Lynx Jan 25 '18 at 21:24
0

You should use Promises like this

function outer() {
    var data = new Promise(resolve => {
        ScriptLoader.getScripts('proj4script', function() {
            var x = proj4(12312312312)
            resolve(x)
        })
    })

    data.then(x => {
        //You have the x here
        console.log(x)
    })
}

Or using the async/await syntax

async function outer() {
    var x = await new Promise(resolve => {
        ScriptLoader.getScripts('proj4script', function() {
            let x = proj4(12312312312)
            resolve(x)
        })
    })

    //You have the x here
    console.log(x)
}

And another solution declaring var x outside the callback

async function outer() {
    var x
    await new Promise(next => {
        ScriptLoader.getScripts('proj4script', function() {
            x = proj4(12312312312)
            next()
        })
    })
    //You have the x with data here
    console.log(x)
}
Fernando Carvajal
  • 1,869
  • 20
  • 19