This question already has an answer here:
I am running an event loop of the following form:
var i;
var j = 10;
for (i = 0; i < j; i++) {
asynchronousProcess(callbackFunction() {
alert(i);
});
}
I am trying to display a series of alerts showing the numbers 0 through 10. The problem is that by the time the callback function is triggered, the loop has already gone through a few iterations and it displays a higher value of i
. Any recommendations on how to fix this?
Here is a sample functional approach to what is expected here.
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 variablei
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 thefor
loop variable and it will be uniquely defined for each iteration of thefor
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
Create Your Own Function Closure Using an IIFE
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 theasynchronousProcess()
function the cntr back to the callback like this:Use ES6
let
If you have a Javascript execution environment that fully supports ES6, you can use
let
in yourfor
loop like this:let
declared in afor
loop declaration like this will create a unique value ofi
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
andawait
, then you have more options.This will make sure that only one call to
asynchronousProcess()
is in flight at a time and thefor
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 useawait
, the containing function must be declaredasync
.JavaScript code runs on a single thread, so you cannot principally block to wait for the first loop iteration to complete before beginning the next without seriously impacting page usability.
The solution depends on what you really need. If the example is close to exactly what you need, @Simon's suggestion to pass
i
to your async process is a good one.async await
is here (ES7), so you can do this kind of things very easily now.Remember, this works only if
asycronouseProcess
is returning aPromise
If
asycronouseProcess
is not in your control then you can make it return aPromise
by yourself like thisThen replace this line
await asycronouseProcess();
byawait asyncProcess();
Understanding
Promises
before even looking intoasync await
is must (Also read about support forasync await
)ES2017: You can wrap the async code inside a function(say XHRPost) returning a promise( Async code inside the promise).
Then call the function(XHRPost) inside the for loop but with the magical Await keyword. :)
Several. You can use bind:
Or, if your browser supports let (it will be in the next ECMAScript version, however Firefox already supports it since a while) you could have:
Or, you could do the job of
bind
manually (in case the browser doesn't support it, but I would say you can implement a shim in that case, it should be in the link above):I usually prefer
let
when I can use it (e.g. for Firefox add-on); otherwisebind
or a custom currying function (that doesn't need a context object).