In this article, we are going to discuss a problem that every single one of us, JavaScript people, has faced or will face eventually.
You run some kind of “for” loop, possibly iterating over an array, you expect to gather some results in another array, and all you get is the result of the last iterated value, or “undefined,” pushed N times in the result array. One possible reason you get these results is running an asynchronous function, or an HTTP call, inside the loop:
Let us have a look at this:
var resultsArray = []
for(var i = 0; i < 5; i++) {
var result = someAsyncFnOrAnHTTPCall(i)
resultsArray.push(result);
}
Looks simple enough. Intuitively, we have an idea of how things would go when it runs. The index variable “i” is 0 in the beginning, the function inside takes it as an argument, does its thing with the 0 value, returns something based on it, and we push that into the array. Then “i” becomes 1, and so on, right?
WRONG.
At least when we’re dealing with any kind of asynchronous operation. You see, JavaScript is still single-threaded. That might not change any time soon (although some steps are taken towards it, but it’s hard to do that without breaking pretty much the entire web). So, processing only one instruction at a time, we need a way to deal with the more complex ones, so they wouldn’t block everything else we need to run. For example, an HTTP call needed to get some data from an external source could take seconds in doing so, and in a modern web environment, we can’t afford to freeze the entire page while waiting for that response.
So, what do we do with async operations? They wait in a queue. When there’s no current synchronous code to be run, their turn comes. Knowing that, here’s what would happen in the example above:
- The loop starts with “i” being 0.
- The “result” variable is declared.
- An async function follows, so it goes to wait in the queue.
- The .push() method is executed. We still have no value for the “result” variable, so it’s “undefined”.
- The loop goes on the same way 4 more times. The “i” variable, on the other hand, goes one round extra without triggering an iteration, so it finishes with a value of 5.
- Now the asynchronous function runs five consecutive times, as instructed – taking the current value of “i” as an argument (5). This, of course, has no effect whatsoever on the “resultsArray”, which is already filled with five undefined values, but running the function with a value that “i” is never intended to reach might have a devastating effect, depending on what you’re doing in that function. If we try to solve the problem by moving the .push() inside the async function, then it will still run five times with the same wrong value, pushing the wrong result in the array – might not be “undefined” this time, but it will nevertheless be the same incorrect result five times.
The solution? IIFE (Immediately Invoked Function Expression) is here to help you:
var resultsArray = []
for(var i = 0; i < 5; i++) {
(function() {
var ii = i;
someAsyncFnThatIncludesPushingTheResultToTheArray(ii);
})();
}
Let’s elaborate:
- We’re initiating the loop in the same way as before.
- We define an IIFE. It’s basically a function that immediately runs itself. We need it because it has its own scope, and when we’re confined in an ES5 environment, we absolutely need that for our “var” variables.
- We re-declare the index as “ii” in the IIFE. “Var” variables have function scope, so they’re perfectly encapsulated when declared in a function. What they lack is a block scope, which makes them vulnerable in a “for” loop. When we take the current “i” value and re-declare it as a scoped variable inside the IIFE, we’re ensuring two things:
- We’ll provide the async function a different argument every time it runs by taking the current “i” value;
- Said argument would never reach an undesirable value as we saw above;
- We run the asynchronous code, which once again goes to the queue, but this time with the right input, so we can expect a correct output from it.
- The correct result is pushed to the array only AFTER we actually have it.
It basically boils down to a good understanding of the timeline of events. So, channel your inner Sarah Connor and IIFE those vars!
For more tips and tricks about ServiceNow and JavaScript, read out our latest article about “GlideAggregate” and its performance tweaks.