0

I've read How do I return the response from an asynchronous call? and understand the nature of asynchronous calls in JavaScript.

Using jQuery 3.2.1, I have some code which makes an ajax call to an endpoint, /get-data, which returns a JSON structure with a property and value, e.g.

{ "download_list_groups_limit" : 2 }

If the JSON response doesn't contain the property download_list_groups_limit then I return a default value, 10 in this case (see code below).

This means that I either have the result of the ajax response (2 in this example) or a default value of 10.

I want to assign the result to a variable called download_list_groups_limit.

I understand the async nature of JavaScript means that the ajax call to /get-data has to complete before the result is available.

I have the following code:

var download_list_groups_limit = getSubstanceLimits(function(result) {
    console.log('result = ' + result);
});

console.log('download_list_groups_limit = ' + download_list_groups_limit);


function getSubstanceLimits(callback) {
    let filtered_response; 

    $.ajax({
        url : '/get-data',
        success: function(response) {
            if (response.download_list_groups_limit) {
                filtered_response = response.download_list_groups_limit; // 2 in this example
            } else {
                filtered_response = 10; // Default
            }
            callback(filtered_response);
        }
    });
}

The console.log statements return the following:

download_list_groups_limit = undefined
result = 2

My question is about scope: If I want to use my result in the global scope of my script (e.g. assign a variable and have it set to 2) how can I do that? It seems the only place I can do that is inside the callback, i.e. inside getSubstanceLimits(function(result) { ... });

If I need to use the value, 2, outside the callback across various points in my script, how is that possible?

Andy
  • 5,142
  • 11
  • 58
  • 131
  • 3
    You can always assign to some variable or object property in an outer scope. The problem is knowing **when** it's OK to access the value. – Pointy Nov 08 '20 at 14:17
  • 1
    You can. Just assign it `download_list_groups_limit = result`. The problem is not that it doesn't work. The problem is you are `console.logging` **too early**. To prove it works try logging it every one second: `setInterval(() => console.log(download_list_groups_limit), 1000)`. – slebetman Nov 08 '20 at 14:25

2 Answers2

2

Change the entry point of your script so that getSubstanceLimits gets called first, before anything else. In the callback, call your previous entry point, which runs the rest of your script. You could do something like:

function getSubstanceLimits(callback) {
    $.ajax({
        url: '/get-data',
        success: function (response) {
            callback(response.download_list_groups_limit || 10);
        }
    });
}

and instead of your prior entry point, eg:

runApp();

do

getSubstanceLimits(runApp);

function runApp(result) {
  // code here that uses async `result`
}

Your whole app doesn't have to be inside the callback, but wherever you need to depend on result needs to be inside the callback.

You could also use the jQuery then-able instead of using a callback.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • There is no `runApp();` in my code so this has complicated it further. At the moment everything is inside `$(document).ready(function () { ... `. How does the equivalent of `getSubstanceLimits(runApp);` occur with that? – Andy Nov 08 '20 at 15:58
  • Then you do have an entry point - the `ready` callback. So you can change to: `getSubstanceLimits( result => $(document).ready(function () { // app here }) ) ` – CertainPerformance Nov 08 '20 at 16:09
0

It's not a scope issue. If all you care about is scope assigning the result to a global variable does work.

Here's an example demonstrating that it works. I'm replacing a single console.log() with logging once per second:

var download_list_groups_limit;

getSubstanceLimits(function(result) {
    download_list_groups_limit = result;
    console.log('result = ' + result);
});

setInterval(function(){
    console.log('download_list_groups_limit = ' + download_list_groups_limit);
}, 1000);

You will certainly see that at some point in time download_list_groups_limit will change to the value of result.

The only reason it does not work with the code you tested is that you were logging download_list_groups_limit too early. (Assuming of course you fix the assignment to be like my example).

If you are OK with code that depends on download_list_groups_limit fail until it gets the correct value then you can simply do what I did above. If that is not OK with you then you need to put the rest of your code inside the callback to getSubstanceLimits. You really need to change your thinking about how to design program flow.

There are several design patterns and language features that can make asynchronous code more manageable but you really need to understand about when things happen in javascript. Your understanding of scope should be already correct if you are familiar with languages like C++ an Java. But your understanding of when things happen needs improvement:

// THIS HAPPENS FIRST
var download_list_groups_limit;

// THIS HAPPENS SECOND
getSubstanceLimits(function(result) {
    // THINGS IN HERE HAPPENS LAST
    // AFTER END OF ALL CODE IN ALL FILES

    download_list_groups_limit = result;
    console.log('result = ' + result);
});

// THIS HAPPENS THIRD
console.log('download_list_groups_limit = ' + download_list_groups_limit);

// END OF CODE HAPPENS FOURTH

After you get yourself used to thinking of things happening in the future I suggest you look at Promises, async/await (which use promises), event listeners and things like Observables etc.

slebetman
  • 109,858
  • 19
  • 140
  • 171