So, here is the basic scenario. You have some sort of for/next loop that then calls some asynchronous function. When the function runs, what you see when the code runs is that the last value of the loop index is the value that gets used in the function for every instance that it gets called.
Here is a really simple example that demonstrates the problem.
But, you will also see it when you try to fire an event.
Or even more common, when you try to make an AJAX call.
For the remainder of this post, we’ll stick with the first example because the problem is the same and the code for that one has the least moving parts.
So, in our working example, when the code actually runs, 10 will get spit out to the console 10 times because by the time the code runs, that is the value that i will have. Maybe you thought it would be 9. But the loop stopped looping because i was 10.
If you think, “OK, so I’ll just make the function fire immediately after I set it up by using setTimeout(func,1), let me remind you that in our second example of firing an event, that is essentially what is happening there. It won’t work either.
Not a Matter of Timing
So, even if we could set a timeout value small enough to execute before the loop will complete, what you have to remember about setTimeout and setInterval is that all we are doing when we make those calls is we are saying, “run this code as soon after the timeout value as possible.” Under the hood it puts the function in the event queue when the timeout value has expired.
One solution is to wrap our code in another closure that will run immediately.
This example is using an IIFE (Immediately Invoked Function Expression) so that the function runs right away. The effect is the same as the original code except for now the variable ii is local to our IIFE so it will not change every time the variable i changes.
Now, by this point, you might be thinking, why not just create a new variable ii inside the loop?
Well, the problem with this is variable hoisting. Any variable you declare within a function, regardless of where it is declared, is physically declared at the top of the function. So, you aren’t really creating a variable local to the loop. You are creating a variable local to the function (or global scope in this case) and you end up with the same problem as before.
But, ES2015 recognizes and has finally provided a means of creating a variable local to a code block rather than just function blocks. To do this, they’ve introduced the LET keyword.
So, if you change your code to:
The problem of course is that there aren’t a lot of browsers that support the LET keyword right now. But there are transpilers that will convert your code from ES2015 to ES5. And the way they do this is our final solution.
The problem with solution 1 is that while it works most of the time, it really isn’t the most reliable way of solving the problem. At the very least it sets up a lot more code that we really need. If we peek under the hood to how the transpilers implement LET, what we see is that they take advantage of the fact that the CATCH block of the try/catch syntax has its own scope.
So, all we have to do is throw i, catch it in the catch block and use the variable we caught in our callback function.
It tends to be a bit cleaner than solution 1 and is the solution I prefer. But, there is another strong reason for using this last solution that most people overlook. When you wrap a function with an IIFE like we’ve done with solution 1, it changes the meaning inside the IIFE of
continue. Using the
try/catch mechanism allows you to treat the code as if it were inline with the code the
try/catch is in. This is probably closer to what you had in mind when you wrote the original code to begin with.
- WebForms vs MVC–The War Is Over - September 25th, 2014
- Create A Desktop Application using Angular, Bootstrap and C# - October 15th, 2015
- Are You Doing Angular Right? - November 5th, 2015
- Adventures Working With Angular’s $scope - November 26th, 2015
- Using Gulp to Bundle, Minify, and Cache-bust - January 28th, 2016
- Angular 2 – First Impressions [Compared to Angular 1] - February 25th, 2016
- Reactions to React JS and Associated Bits - March 17th, 2016
- An Explanation of the Flux Pattern - March 31st, 2016
- Ext JS 6 by Sencha - The Good, The Bad, The Ugly - April 7th, 2016
- Do This To Increase Your Client Side Web Development Speed - April 21st, 2016
- ES2015 Code Coverage and Jest (React JS Unit Testing) - May 5th, 2016
- 4 Reasons To Drop MVVM - July 27th, 2016
- You Can Start Using Node Today - August 2nd, 2016
- TypeScript and Electron The Right Way - September 6th, 2016