Adarsh Konchady
Adarsh Konchady

Reputation: 2737

Javascript - Handling race condition on async function call

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

Answers (3)

Patrick Evans
Patrick Evans

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

T.J. Crowder
T.J. Crowder

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:

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

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

Evan Davis
Evan Davis

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

Related Questions