When Using Callbacks Inside a Loop in JavaScript, How to Save a Variable That's Updated in the Loop for Use in the Callback

When using callbacks inside a loop in javascript, is there any way to save a variable that's updated in the loop for use in the callback?

A common, if ugly, way of dealing with this situation is to use another function that is immediately invoked to create a scope to hold the variable.

for(var i = 0; i < length; i++) {
var variable = variables[i];
otherVariable.doSomething(function(v) { return function(err) { /* something with v */ }; }(variable));
}

Notice that inside the immediately invoked function the callback that is being created, and returned, references the parameter to the function v and not the outside variable. To make this read much better I would suggest extracting the constructor of the callback as a named function.

function callbackFor(v) {
return function(err) { /* something with v */ };
}
for(var i = 0; i < length; i++) {
var variable = variables[i];
otherVariable.doSomething(callbackFor(variable));
}

JavaScript closure inside loops – simple practical example

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

ES6 solution: let

ECMAScript 6 (ES6) introduces new let and const keywords that are scoped differently than var-based variables. For example, in a loop with a let-based index, each iteration through the loop will have a new variable i with loop scope, so your code would work as you expect. There are many resources, but I'd recommend 2ality's block-scoping post as a great source of information.

for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}

Beware, though, that IE9-IE11 and Edge prior to Edge 14 support let but get the above wrong (they don't create a new i each time, so all the functions above would log 3 like they would if we used var). Edge 14 finally gets it right.



ES5.1 solution: forEach

With the relatively widespread availability of the Array.prototype.forEach function (in 2015), it's worth noting that in those situations involving iteration primarily over an array of values, .forEach() provides a clean, natural way to get a distinct closure for every iteration. That is, assuming you've got some sort of array containing values (DOM references, objects, whatever), and the problem arises of setting up callbacks specific to each element, you can do this:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});

The idea is that each invocation of the callback function used with the .forEach loop will be its own closure. The parameter passed in to that handler is the array element specific to that particular step of the iteration. If it's used in an asynchronous callback, it won't collide with any of the other callbacks established at other steps of the iteration.

If you happen to be working in jQuery, the $.each() function gives you a similar capability.



Classic solution: Closures

What you want to do is bind the variable within each function to a separate, unchanging value outside of the function:

var funcs = [];

function createfunc(i) {
return function() {
console.log("My value: " + i);
};
}

for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}

Asynchronous Process inside a javascript for loop

The for loop runs immediately to completion while all your asynchronous operations are started. When they complete some time in the future and call their callbacks, the value of your loop index variable i will be at its last value for all the callbacks.

This is because the for loop does not wait for an asynchronous operation to complete before continuing on to the next iteration of the loop and because the async callbacks are called some time in the future. Thus, the loop completes its iterations and THEN the callbacks get called when those async operations finish. As such, the loop index is "done" and sitting at its final value for all the callbacks.

To work around this, you have to uniquely save the loop index separately for each callback. In Javascript, the way to do that is to capture it in a function closure. That can either be done be creating an inline function closure specifically for this purpose (first example shown below) or you can create an external function that you pass the index to and let it maintain the index uniquely for you (second example shown below).

As of 2016, if you have a fully up-to-spec ES6 implementation of Javascript, you can also use let to define the for loop variable and it will be uniquely defined for each iteration of the for loop (third implementation below). But, note this is a late implementation feature in ES6 implementations so you have to make sure your execution environment supports that option.

Use .forEach() to iterate since it creates its own function closure

someArray.forEach(function(item, i) {
asynchronousProcess(function(item) {
console.log(i);
});
});

Create Your Own Function Closure Using an IIFE

var j = 10;
for (var i = 0; i < j; i++) {
(function(cntr) {
// here the value of i was passed into as the argument cntr
// and will be captured in this function closure so each
// iteration of the loop can have it's own value
asynchronousProcess(function() {
console.log(cntr);
});
})(i);
}

Create or Modify External Function and Pass it the Variable

If you can modify the asynchronousProcess() function, then you could just pass the value in there and have the asynchronousProcess() function the cntr back to the callback like this:

var j = 10;
for (var i = 0; i < j; i++) {
asynchronousProcess(i, function(cntr) {
console.log(cntr);
});
}

Use ES6 let

If you have a Javascript execution environment that fully supports ES6, you can use let in your for loop like this:

const j = 10;
for (let i = 0; i < j; i++) {
asynchronousProcess(function() {
console.log(i);
});
}

let declared in a for loop declaration like this will create a unique value of i for each invocation of the loop (which is what you want).

Serializing with promises and async/await

If your async function returns a promise, and you want to serialize your async operations to run one after another instead of in parallel and you're running in a modern environment that supports async and await, then you have more options.

async function someFunction() {
const j = 10;
for (let i = 0; i < j; i++) {
// wait for the promise to resolve before advancing the for loop
await asynchronousProcess();
console.log(i);
}
}

This will make sure that only one call to asynchronousProcess() is in flight at a time and the for loop won't even advance until each one is done. This is different than the previous schemes that all ran your asynchronous operations in parallel so it depends entirely upon which design you want. Note: await works with a promise so your function has to return a promise that is resolved/rejected when the asynchronous operation is complete. Also, note that in order to use await, the containing function must be declared async.

Run asynchronous operations in parallel and use Promise.all() to collect results in order

 function someFunction() {
let promises = [];
for (let i = 0; i < 10; i++) {
promises.push(asynchonousProcessThatReturnsPromise());
}
return Promise.all(promises);
}

someFunction().then(results => {
// array of results in order here
console.log(results);
}).catch(err => {
console.log(err);
});

How can you create a closure in a loop that which captures variables and takes parameters in Javascript?

I typically externalize the function, pass it the data I want to keep around and return a function that accepts the handlers parameters:

function getOnClickHandler(data) {
return function(params) {
console.log(data);
console.log(params);
}
}

for (data in data_list) {
var network = new vis.Network(data['container'], data['data'], data['options']);
network.on("click", getOnClickHandler(data));
}

How do you carry mutating data into callbacks within loops?

If you want to iterate one async operation at a time, you cannot use a while loop. Asynchronous operations in Javascript are NOT blocking. So, what your while loop does is run through the entire loop calling checkInput() on every value and then, at some future time, each of the callbacks get called. They may not even get called in the desired order.

So, you have two options here depending upon how you want it to work.

First, you could use a different kind of loop that only advances to the next iteration of the loop when the async operation completes.

Or, second, you could run them all in a parallel like you were doing and capture the state of your object uniquely for each callback.

I'm assuming that what you probably want to do is to sequence your async operations (first option).

Sequencing async operations

Here's how you could do that (works in either ES5 or ES6):

function next() {
if (input.notEnd()) {
input.next();
checkInput(input, success => {
if (success) {
// because we are still on the current iteration of the loop
// the value of input is still valid
console.log(`Input ${input.combo} works!`);
}
// do next iteration
next();
});
}
}
next();

Run in parallel, save relevant properties in local scope in ES6

If you wanted to run them all in parallel like your original code was doing, but still be able to reference the right input.combo property in the callback, then you'd have to save that property in a closure (2nd option above) which let makes fairly easy because it is separately block scoped for each iteration of your while loop and thus retains its value for when the callback runs and is not overwritten by other iterations of the loop (requires ES6 support for let):

while(input.notEnd()) {
input.next();
// let makes a block scoped variable that will be separate for each
// iteration of the loop
let combo = input.combo;
checkInput(input, (success) => {
if (success) {
console.log(`Input ${combo} works!`);
}
});
}

Run in parallel, save relevant properties in local scope in ES5

In ES5, you could introduce a function scope to solve the same problem that let does in ES6 (make a new scope for each iteration of the loop):

while(input.notEnd()) {
input.next();
// create function scope to save value separately for each
// iteration of the loop
(function() {
var combo = input.combo;
checkInput(input, (success) => {
if (success) {
console.log(`Input ${combo} works!`);
}
});
})();
}

Index loop does not change

It's because of closures in javascript.

This answer should help explain it and there are a couple ways to handle it.

The easiest being to use .forEach()

data.forEach(function(d) { // get all the messages from username, devfined in search term

var retweetId_str = d.id_str; // id_str = id not rounded more precise
console.log('id_str:', retweetId_str); //
con.query('SELECT retweetid_str FROM droid_retweets WHERE retweetId_str=?', retweetId_str, function(error,result){
console.log('id_str:', retweetId_str);

});

}); // for close

JavaScript Variable Stuck inside Loop - Can't Access

That is an asynchronous $.get function. That is to say, code execution will not wait at that line until the $.get is done, perform the callback function, and then continue onto the next line after the $.get. In reality, it will perform an asynchronous call to the $.get function, and continue execution immediately to the console.log(fullHTML) line. There is no guarantee at this point that the $.get function has finished or even started, as it's being executed asynchronously.

As for your issue with the accessibility to j, that's a problem that comes back to closures. Your j variable, when accessed inside of the $.get callback, will reference the j variable that has been looped through already, and will return the incorrect j value, as you probably want the value of j from when the $.get request was executed. So, you'll want to store j inside of the local closure by declaring it explicitly like var current = j, the instead of referencing j inside of your $.get callback, you'd reference current, and it would perform as intended. This doesn't work!

EDIT

We can force create a new scope if we declare an anonymous function and execute the $.get inside of that context. It's not a pretty solution, but it works!

To fix your code, put your calls inside of the $.get callback like so:

var states = ['TX', 'AZ'];

for(j = 0; j < states.length; j++) {
var fullHTML = '';
var full2;
(function() {
var current = j;
$.get("//127.0.0.1:8000/api/state-stores/", {state: states[j]}, function(data) {
console.log(data.stores.length);

for(var i = 0; i < data.stores.length; i++) {
store_html = '<tr><td>' + data.stores[i].name + '</td><td>'
fullHTML += store_html;
}
console.log(fullHTML); // not empty anymore
$("#" + states[current].toLowerCase() + "-table").html(fullHTML); //does stuff
});
})();
}

Now, your code will only perform the lines that were broken before AFTER it has populated the fullHTML variable.

Callback after all asynchronous forEach callbacks are completed

Array.forEach does not provide this nicety (oh if it would) but there are several ways to accomplish what you want:

Using a simple counter

function callback () { console.log('all done'); }

var itemsProcessed = 0;

[1, 2, 3].forEach((item, index, array) => {
asyncFunction(item, () => {
itemsProcessed++;
if(itemsProcessed === array.length) {
callback();
}
});
});

(thanks to @vanuan and others) This approach guarantees that all items are processed before invoking the "done" callback. You need to use a counter that gets updated in the callback. Depending on the value of the index parameter does not provide the same guarantee, because the order of return of the asynchronous operations is not guaranteed.

Using ES6 Promises

(a promise library can be used for older browsers):

  1. Process all requests guaranteeing synchronous execution (e.g. 1 then 2 then 3)

    function asyncFunction (item, cb) {
    setTimeout(() => {
    console.log('done with', item);
    cb();
    }, 100);
    }

    let requests = [1, 2, 3].reduce((promiseChain, item) => {
    return promiseChain.then(() => new Promise((resolve) => {
    asyncFunction(item, resolve);
    }));
    }, Promise.resolve());

    requests.then(() => console.log('done'))
  2. Process all async requests without "synchronous" execution (2 may finish faster than 1)

    let requests = [1,2,3].map((item) => {
    return new Promise((resolve) => {
    asyncFunction(item, resolve);
    });
    })

    Promise.all(requests).then(() => console.log('done'));

Using an async library

There are other asynchronous libraries, async being the most popular, that provide mechanisms to express what you want.

Edit


The body of the question has been edited to remove the previously synchronous example code, so i've updated my answer to clarify.
The original example used synchronous like code to model asynchronous behaviour, so the following applied:

array.forEach is synchronous and so is res.write, so you can simply put your callback after your call to foreach:

  posts.foreach(function(v, i) {
res.write(v + ". index " + i);
});

res.end();


Related Topics



Leave a reply



Submit