Asynchronous code inside an array loop

July 16, 2017 0 Comments

Asynchronous code inside an array loop

 

 

Async is one of the most important tool for Js developer. And since it’s flow is not procedural like normally seen in Js code, async code is a bit harder to follow. In this article, I will present a common case where loop and async function interact.

I have to make a loop through an array, then with each item make an async data call and do some calculation with the index inside the callback function. A similar situation can be found below:

var output = '';
var array = ['h','e','l','l','o'];
// This is an example of an async function, like an AJAX call
var fetchData = function () {
return new Promise(function (resolve, reject) {
resolve();
});
};
/ 
Inside the loop below, we execute an async function and create
a string from each letter in the callback.
   - Expected output: hello
- Actual output: undefinedundefinedundefinedundefined

/
for (var i = 0; i < array.length; i++) {
fetchData(array[i]).then(function () {
output += array[i];
});
}

We expect that as i increases, each item of the “hello” array will be added to the list. In reality though, we got a string of 4 undefined.

Below is the order the loop will be executed. You can see line output += array[i]; doesn’t run in normal top-to-bottom order, since it’s in a async callback

Main execution order
How value of i changes when callback is involved

Because async callback runs when the promise is resolved, outside of the main execution order, when it runs, i is already equals array.length, as you can see in the chart. Therefore array[i] returns undefined :(

A variable’s scope defines the part of the program where the variable is accessible. For example, the classic case of variable scope is variable created inside a function

function sayHello() {
var hello = "Hello";
console.log(hello); // output: "Hello"
}
console.log(hello);  // output: undefined

An important notice here is that JavaScript traditionally does not support block level scoping, for example

for (var i = 0; i < 4; i++) {
// Do sth cool with i
}
console.log(i); // output: 4

Variable i is available outside of the loop block. The loop runs 4 times until i===4 where the condition fails. The same situation happens to our async loop above.

The principle to solve this problem is to put the array[i] inside a scope, to prevent it from changing when value of i changes. There are 2 ways to do this

Save array[i] in a scoped variable by creating a function

As function variable is scoped, you can create a function wrap around the async call. This way, array[i] is saved, and you get the intended output.

function appendOutput (item) {
fetchData(item).then(function () {
output.innerHTML += item;
});
}
for (var i = 0; i < array.length; i++) {
appendOutput(array[i]); // output: hello
}

Use ES6 :)

Fortunately, since ES6 (ES2015) you can use let to define a variable and it’s scoped in block level. So if your project is running with ES6, this will be the simple solution

for (let i = 0; i < array.length; i++) {
fetchData(array[i]).then(function () {
output.innerHTML += array[i]; // output: hello
});
}

Javascript is deceptively simple, but there’s a lower level of understanding (such as scope and memory) that when acquires, can help solve many “random” bugs. With this short article, I hope you has a better understanding of async and relieve some headaches of your own.


Tag cloud