Why Is My Variable Unaltered After I Modify It Inside of a Function? - Asynchronous Code Reference

Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

One word answer: asynchronicity.

Forewords

This topic has been iterated at least a couple of thousands of times, here, in Stack Overflow. Hence, first off I'd like to point out some extremely useful resources:

  • @Felix Kling's answer to "How do I return the response from an asynchronous call?". See his excellent answer explaining synchronous and asynchronous flows, as well as the "Restructure code" section.

    @Benjamin Gruenbaum has also put a lot of effort explaining asynchronicity in the same thread.

  • @Matt Esch's answer to "Get data from fs.readFile" also explains asynchronicity extremely well in a simple manner.


The answer to the question at hand

Let's trace the common behavior first. In all examples, the outerScopeVar is modified inside of a function. That function is clearly not executed immediately, it is being assigned or passed as an argument. That is what we call a callback.

Now the question is, when is that callback called?

It depends on the case. Let's try to trace some common behavior again:

  • img.onload may be called sometime in the future, when (and if) the image has successfully loaded.
  • setTimeout may be called sometime in the future, after the delay has expired and the timeout hasn't been canceled by clearTimeout. Note: even when using 0 as delay, all browsers have a minimum timeout delay cap (specified to be 4ms in the HTML5 spec).
  • jQuery $.post's callback may be called sometime in the future, when (and if) the Ajax request has been completed successfully.
  • Node.js's fs.readFile may be called sometime in the future, when the file has been read successfully or thrown an error.

In all cases, we have a callback which may run sometime in the future. This "sometime in the future" is what we refer to as asynchronous flow.

Asynchronous execution is pushed out of the synchronous flow. That is, the asynchronous code will never execute while the synchronous code stack is executing. This is the meaning of JavaScript being single-threaded.

More specifically, when the JS engine is idle -- not executing a stack of (a)synchronous code -- it will poll for events that may have triggered asynchronous callbacks (e.g. expired timeout, received network response) and execute them one after another. This is regarded as Event Loop.

That is, the asynchronous code highlighted in the hand-drawn red shapes may execute only after all the remaining synchronous code in their respective code blocks have executed:

async code highlighted

In short, the callback functions are created synchronously but executed asynchronously. You just can't rely on the execution of an asynchronous function until you know it has executed, and how to do that?

It is simple, really. The logic that depends on the asynchronous function execution should be started/called from inside this asynchronous function. For example, moving the alerts and console.logs too inside the callback function would output the expected result, because the result is available at that point.

Implementing your own callback logic

Often you need to do more things with the result from an asynchronous function or do different things with the result depending on where the asynchronous function has been called. Let's tackle a bit more complex example:

var outerScopeVar;
helloCatAsync();
alert(outerScopeVar);

function helloCatAsync() {
setTimeout(function() {
outerScopeVar = 'Nya';
}, Math.random() * 2000);
}

Note: I'm using setTimeout with a random delay as a generic asynchronous function, the same example applies to Ajax, readFile, onload and any other asynchronous flow.

This example clearly suffers from the same issue as the other examples, it is not waiting until the asynchronous function executes.

Let's tackle it implementing a callback system of our own. First off, we get rid of that ugly outerScopeVar which is completely useless in this case. Then we add a parameter which accepts a function argument, our callback. When the asynchronous operation finishes, we call this callback passing the result. The implementation (please read the comments in order):

// 1. Call helloCatAsync passing a callback function,
// which will be called receiving the result from the async operation
helloCatAsync(function(result) {
// 5. Received the result from the async function,
// now do whatever you want with it:
alert(result);
});

// 2. The "callback" parameter is a reference to the function which
// was passed as argument from the helloCatAsync call
function helloCatAsync(callback) {
// 3. Start async operation:
setTimeout(function() {
// 4. Finished async operation,
// call the callback passing the result as argument
callback('Nya');
}, Math.random() * 2000);
}

Code snippet of the above example:

// 1. Call helloCatAsync passing a callback function,//    which will be called receiving the result from the async operationconsole.log("1. function called...")helloCatAsync(function(result) {    // 5. Received the result from the async function,    //    now do whatever you want with it:    console.log("5. result is: ", result);});
// 2. The "callback" parameter is a reference to the function which// was passed as argument from the helloCatAsync callfunction helloCatAsync(callback) { console.log("2. callback here is the function passed as argument above...") // 3. Start async operation: setTimeout(function() { console.log("3. start async operation...") console.log("4. finished async operation, calling the callback, passing the result...") // 4. Finished async operation, // call the callback passing the result as argument callback('Nya'); }, Math.random() * 2000);}

Variable out of scope (probably) - undefined

Use res inside the axios like below. You are getting undefined because the desc scope ends inside that anonymous function. Moreover the axios is async. You may need to use await to make it sync or can follow the idea I proposed below.

const item = [];
var desc;

app.post('/test', (req, res0) => {
const { word } = req.body;

if(!word) {
res0.status(418).send({error: 'no word provided'})
}

console.log(word)

axios(`https://sjp.pl/${word}`).then(res => {

const html = res.data
const $ = cheerio.load(html)

$('p').each(function() {
item.push($(this).text())
})

desc = item[3]

res0.send({
description: `${desc}`
})
})
})

How to resolve asynchronous variable?

When you code with this pattern

function doIt () {
var something = ''
invokeFunction()
.then (function handleResult (result) {
console.log('2', something)
something = result
} )
console.log('1', something)
}

your console.log('1', something) runs before the inner function (I named it handleResult for clarity) is ever called. That's because invokeFunction() is asynchronous; it returns a Promise object.

You could rewrite this as follows:

async function doIt () {
var something = await invokeFunction()
console.log('1', something)
}

to get the kind of result you want.

With respect, you will have a very hard time coding Javascript unless you take the time to learn about async / await and Promises. In that respect the Javascript language is very different from other languages. Drop everything and learn that stuff now. Seriously.

Why is a global variable not being updated inside a function that is NOT redeclaring it as a local variable?

You can declare a function after your script and call that function when the XHR request is done and you've mutated the variable.

/* ... */
pendingPaymentTrigger = true;
pendingPayment = Math.round((obj.body.primary.payments.balances + obj.body.primary.payments.generate + obj.body.primary.payments.immature) * 100) / 100;
doSomething();
/* ... */
function doSomething() {
console.log(pendingPayment);
}


Related Topics



Leave a reply



Submit