Reputation: 2737
This weekend, I ran into a peculiar problem of handling race conditions in Javascript.
Here is the code that was giving me problem:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, function(data) {
//Callback for the ajax call
//PROBLEM : HOW CAN I ACCESS VALUE OF 'i' here for the iteration for which the AJAX call was made!!!
}
}
}
Accessing the value of 'i' inside the AJAX callback would not give me the expected value because by the time AJAX call response comes back, the 'for' loop would have crossed many more iterations.
To handle this, I used the following approach:
function myFunction(requestObj, myArray) {
var i = 0;
function outerFunction() {
makeAjaxCall(requestObj, innerFunction);
}
function innerFunction(data) {
i++;
if(i<myArray.length) {
outerFunction();
}
}
outerFunction();
}
Is this the correct approach? Any other way I can improve this assuming it is a 3rd pary AJAX library call which I can't modify.
Upvotes: 0
Views: 391
Reputation: 42736
There are two other ways of doing this: using bind, using a closure
Using bind:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
makeAjaxCall(requestObj, function(idx,data) {
//idx will be correct value
}.bind(null,i));
}
}
Using a closure:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
(function(idx){
makeAjaxCall(requestObj, function(data) {
//idx will be correct value
});
})(i);
}
}
There is also third method, use another function to create your callback
function myFunction(requestObj, myArray) {
function makeCB(idx){
return function(){
//do stuff here
}
}
for(var i=0;i<myArray.length;i++) {
makeAjaxCall(requestObj, makeCB(i));
}
}
Upvotes: 0
Reputation: 1073978
The issue is that the callbacks you're passing to the ajax call have an enduring reference to i
, not a copy of its value when they were created.
Your approach is fine except that it waits to make the second ajax call until the first finishes, then waits for the second to finish before the third, etc. Unless you have to do that (and I get the impression you don't), it's better to let them overlap.
A couple of options:
Use a builder function:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, buildHandler(i));
}
function buildHandler(index) {
return function(data) {
// Use `index` here
};
}
}
Now, the handler has a reference to index
, which doesn't change, rather than i
, which does.
Use Function#bind
:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call
makeAjaxCall(requestObj, function(index, data) {
// Use index here
}.bind(null, i));
}
}
Function#bind
creates a function that, when called, will call the original function with a specific this
value (we're not using that above) and any arguments you pass bind
— followed by any arguments given to the bound function.
I prefer #1: It's clear to read and doesn't create a bunch of unnecessary functions (whereas in theory, #2 creates two functions per loop rather than just one).
Upvotes: 0
Reputation: 36592
You just need to use a closure:
function myFunction(requestObj, myArray) {
for(var i=0;i<myArray.length;i++) {
//AJAX call closed over i
(function(i) { // wrap your call in an anonymous function
makeAjaxCall(requestObj, function(data) {
// i is what you think it is
}
})(i) // pass i to the anonymous function and invoke immediately
}
}
Upvotes: 4