In the current code I’m working on, I had to iterate over an array and execute an Ajax request for each of these elements.
Then, I had to do an action on that element once the Ajax request resolved.
Let me show you what it looked like at first.
The code
1 | function Sample() { |
NOTE: I’m targeting a GitHub API for demo purposes only. Nothing to do with the actual project.
So nothing is especially bad. Nothing is flagged in JSHint and I’m expecting to have the product names displayed sequentially.
Expected output
1 | first product second product third product |
Commit and deploy, right? Well, no.
Here’s what I got instead.
Actual output
1 | third product third product third product |
What the hell is going here?
Explanation of the issue
First, everything has to do with scope. var
are scoped to the closest function
definition or the global scope depending on where the code is being executed. In our example, var is scoped to a function above the for
loop.
Then, we have the fact that variables can be declared multiple times and only the last value is taken into account. So every time we loop, we redefine product
to be the current instance of products[i]
.
By the time an Http request comes back, product
has already been (re-)defined 3 times and it only takes into account the last value.
Here’s a quick timeline:
- Start loop
- Declare
product
and initialize withproducts[0]
- Start http request 1.
- Declare
product
and initialize withproducts[1]
- Start http request 2.
- Declare
product
and initialize withproducts[2]
- Start http request 3.
- Resolve HTTP Request 1
- Resolve HTTP Request 2
- Resolve HTTP Request 3
HTTP requests are asynchronous slow operations and will only resolve after the local code is finished executing. The side-effect is that our product
has been redefined by the time the first request comes back.
Ouch. We need to fix that.
Fixing the issue the old way
If you are coding for the browser in 2016, you want to use closures. Basically, passing the current value in a function that is executed when defined. That function will return the appropriate function to execute. That solves your scope issue.
1 | function Sample() { |
Fixing the issue the new way
If you are using a transpiler like BabelJS, you might want to use ES6 with let
variable instead.
Their scoping is different and way more sane than their var
equivalent.
You can see on BabelJS and TypeScript that the actual problem was resolved in a similar way.
1 | function Sample() { |
Time for me to use a transpiler?
I don’t know if, for me, it’s the straw that will break the camel’s back. I’m really starting to consider using a transpiler that will make our code more readable and less buggy.
This is definitely going on my TODO list.
What about you guys? Have you encountered bugs that would not have happened with a transpiler? Leave a comment!