Asynchronous code inside an array loop

Vu Dao
codeburst
Published in
3 min readJul 16, 2017

--

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.

The situation

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 callvar 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.

A simplified explanation

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 :(

The underline reason: Variable scope

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.

Solutions

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.

--

--