JavaScript is single threaded
Yes, but.. it is also heavily asynchronous. Since my experience porting Node.js to new platforms, it’s turned into one of my go to languages for hacking web apps. Unfortunately I still fall into the async trap.
Let’s look at some code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function myFunction(value, callback) { setTimeout(function () { console.log('Processing value: ' + value); callback(); }, 2000); } var array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; for (var i = 0; i < array.length; i++) { var item = array[i]; myFunction(item, function () { console.log('Done processing value: ' + item); }); } |
And here is the output of the code if we run it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ node bad.js Processing value: 1 Done processing value: 9 Processing value: 2 Done processing value: 9 Processing value: 3 Done processing value: 9 Processing value: 4 Done processing value: 9 Processing value: 5 Done processing value: 9 Processing value: 6 Done processing value: 9 Processing value: 7 Done processing value: 9 Processing value: 8 Done processing value: 9 Processing value: 9 Done processing value: 9 |
Now, in this example – it’s pretty clear what is causing the issue. The setTimeout()
is doing a delay of 2 seconds. This is an async call, so the for
loop has completed it’s entire cycle before any of the callbacks get a chance to run. Timing issues also make things more confusing if things sometimes work since you can’t guarantee the order of asynchronous functions.
The trap for me, is when the code is more complex and it isn’t obvious to see that there is an async call in the way. Also, my brain keeps telling me that for each iteration through the loop, I (think I’m) creating a new variable (item
) and that should provide correct isolation for the state.
There are two simple solutions to this problem.
Solution 1 – use the call stack:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function myFunction(value, callback) { setTimeout(function () { console.log('Processing value: ' + value); callback(); }, 2000); } function helper(item) { myFunction(item, function () { console.log('Done processing value: ' + item); }); } var array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; for (var i = 0; i < array.length; i++) { var item = array[i]; helper(item); } |
Move the async call myFunction
into a helper and pass the value to it. This moves it from being a local variable to one on the stack (as a parameter to helper).
Solution 2 – have the callback give the value back:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function myFunction(value, callback) { setTimeout(function () { console.log('Processing value: ' + value); callback(value); }, 2000); } var array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; for (var i = 0; i < array.length; i++) { var item = array[i]; myFunction(item, function (value) { console.log('Done processing value: ' + value); }); } |
To be honest, solution 2 is just another call stack approach – but it’s a different enough pattern to be a second solution.
The output of both good solutions looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ node good.js Processing value: 1 Done processing value: 1 Processing value: 2 Done processing value: 2 Processing value: 3 Done processing value: 3 Processing value: 4 Done processing value: 4 Processing value: 5 Done processing value: 5 Processing value: 6 Done processing value: 6 Processing value: 7 Done processing value: 7 Processing value: 8 Done processing value: 8 Processing value: 9 Done processing value: 9 |