For loop not being processed in array order [duplicate] - arrays

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
The community reviewed whether to reopen this question 3 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
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?

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);
});

async await is here
(ES7), so you can do this kind of things very easily now.
var i;
var j = 10;
for (i = 0; i < j; i++) {
await asycronouseProcess();
alert(i);
}
Remember, this works only if asycronouseProcess is returning a Promise
If asycronouseProcess is not in your control then you can make it return a Promise by yourself like this
function asyncProcess() {
return new Promise((resolve, reject) => {
asycronouseProcess(()=>{
resolve();
})
})
}
Then replace this line await asycronouseProcess(); by await asyncProcess();
Understanding Promises before even looking into async await is must
(Also read about support for async await)

Any recommendation on how to fix this?
Several. You can use bind:
for (i = 0; i < j; i++) {
asycronouseProcess(function (i) {
alert(i);
}.bind(null, i));
}
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:
for (i = 0; i < j; i++) {
let k = i;
asycronouseProcess(function() {
alert(k);
});
}
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):
for (i = 0; i < j; i++) {
asycronouseProcess(function(i) {
return function () {
alert(i)
}
}(i));
}
I usually prefer let when I can use it (e.g. for Firefox add-on); otherwise bind or a custom currying function (that doesn't need a context object).

var i = 0;
var length = 10;
function for1() {
console.log(i);
for2();
}
function for2() {
if (i == length) {
return false;
}
setTimeout(function() {
i++;
for1();
}, 500);
}
for1();
Here is a sample functional approach to what is expected here.

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. :)
let http = new XMLHttpRequest();
let url = 'http://sumersin/forum.social.json';
function XHRpost(i) {
return new Promise(function(resolve) {
let params = 'id=nobot&%3Aoperation=social%3AcreateForumPost&subject=Demo' + i + '&message=Here%20is%20the%20Demo&_charset_=UTF-8';
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
console.log("Done " + i + "<<<<>>>>>" + http.readyState);
if(http.readyState == 4){
console.log('SUCCESS :',i);
resolve();
}
}
http.send(params);
});
}
(async () => {
for (let i = 1; i < 5; i++) {
await XHRpost(i);
}
})();

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.

Related

How do I set a delay on a file upload in React?

I want to batch my records for uploading so I don't create server issues. I would like to be able to push 10 records at a time, every five seconds, just to prove the concept for now. I've put setInterval functions all over my code but can't get it to run at the right time. I've been at this for days but can't figure it out.
chunkData(data) {
const maxRecords = 10;
const loops = (data.length % maxRecords > 0) ? Math.floor(data.length / maxRecords) + 1 : data.length / maxRecords;
//console.log('data: ', data);
//console.log('loops: ', loops);
//setInterval(() => {
for (let loop = 0; loop < loops; loop++) {
console.log('loop: ', loop);
let start = loop * maxRecords;
//setInterval(() => {
for (let batch = start; batch < start + maxRecords; batch++) {
// the line below will become the upload function once I get this to work
if (data[batch] !== undefined) console.log('data[batch]: ', data[batch]);
}
//start = start + 10;
//}, 5000);
}
//}, 5000);
}
I'm certain it's a simple tweak I need but I'm clueless as to how to make it happen right now. Any advice would be greatly appreciated.
The bigger problem you'll have to figure out is that a client-side change will not help in this case. If you're trying to help your server this way, what happens when there is more than 1 concurrent user uploading? 2? 3? 100? 1000? This solution isn't scalable. You'll eventually (or very quickly) have to make sure your server is robust enough to handle upload traffic.
As for your specific code. Your problem is that you're using setInterval inside a for-loop but use the same value. Remember, uploading (or any XHR/fetch request) is an asynchronous action. Right now, you're setting the intervals to run at basically the same time.
To get actual intervals between uploads, you'd need something like this:
for (let loop = 0; loop < loops; loop++) {
console.log('loop: ', loop);
let start = loop * maxRecords;
for (let i=1, batch = start; batch < start + maxRecords; i++, batch++) {
// the line below will become the upload function once I get this to work
if (data[batch] !== undefined) {
setInterval(() => {
//make upload request here
}, (loop + 1) * i * 5000);
}
}
}
I'm not sure what your "start" variable is supposed to be.
In any case, this code is really error prone and fragile. I really advise reconsidering your approach and look into fixing your server side.
If you still wish to go with this client-side hack and even if not, and you're looking for a more stable client-side solution. I advise to go with react-uploady. It takes care of the uploads for you and all the edge cases that come with managing uploads in React.
You can even do your intervals easily:
import ChunkedUploady, { useChunkStartListener } from "#rpldy/chunked-uploady";
import UploadButton from "#rpldy/upload-button";
const CHUNK_SIZE = 1e+6;
const UploadButtonDelayedChunks = () => {
useChunkStartListener(() => new Promise((resolve) => {
//delays chunk upload by 5 seconds
setTimeout(resolve, 5000);
}));
return <UploadButton/>;
};
export const ChunkedExample = () => {
return (
<ChunkedUploady
destination={{ url: "https://my-server/upload" }}
chunkSize={CHUNK_SIZE}>
<UploadButtonDelayedChunks/>
</ChunkedUploady>
);
};

Strongloop promise inside loop

I am trying to call a loopback find function inside of a for loop, passing in a value from the iteration into the loopback function. The main issue of the code can be represented by the following:
for (var a = 0; a < $scope.countries.length; a++) {
$scope.getEmFacPurElec($scope.countries[a], 'ton/kWh', 'CO2e').then(function(result) {
emFacPurElecToUse = $scope.emFacPurElecs;
}
And here is the function being called:
$scope.getEmFacPurElec = function (country, unit, ghgType) {
var defer = $q.defer();
$scope.emFacPurElecs = [];
$scope.emFacPurElecs = Country.emFacPurElecs({
id: country.id,
filter: {
where: {
and: [
{unit: unit},
{ghgType: ghgType}
]
}
}
});
defer.resolve('Success getEmFacPurElec');
return defer.promise;
};
The problem is that the loopback promise function is called and then returned undefined which means that it moves to the next iteration of the for loop before getting the value to assign to emFacPurElecToUse. I need to do some more calculations with that variable for that country before moving to the next country.
I have looked at using $q.all as a possible solution and also using array.map as per http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html (Rookie mistake #2: WTF, how do I use forEach() with promises?), but I just cannot figure out how to pull it all together to make it work. Should I be using a forEach instead?
I also saw this link angular $q, How to chain multiple promises within and after a for-loop (along with other similar ones) but I do not have multiple promises that I need to process inside the for loop. I need to retrieve the value of one emFacPurElecs for that country, do some work with it, then move to the next country. I feel I am close but I just cannot get my head around how I would code this particular functionality. Any help is greatly appreciated.
It seems to me that you do have multiple promises to process inside your for loop, as you say "I need to do some more calculations with that variable for that country before moving to the next country." This should all be done with in the promise chain that I've suggested - calcEmFacPurElec.
$scope.calcEmFacPurElec = function (country, unit, ghgType) {
$scope.getEmFacPurElec(country, unit, ghgType).then(function(countryEmFacPurElecs) {
// do something with countryEmFacPurElecs
return countryEmFacPurElecs;
}
$scope.getEmFacPurElec = function (country, unit, ghgType) {
var defer = $q.defer();
defer.resolve(Country.emFacPurElecs({
id: country.id,
filter: {
where: {
and: [
{unit: unit},
{ghgType: ghgType}
]
}
}
}); );
return defer.promise;
};
Hopefully the above is a pointer in the right direction!
When you want to carry out a promise chain on an array of items, then as you have identified, Promise.all (using whatever promises implementation you require) is what you want. .all takes in an array of Promises, so in your for loop you can do:
var promises = [];
for (var a = 0; a < $scope.countries.length; a++) {
promises.push($scope.calcEmFacPurElec($scope.countries[a], 'ton/kWh', 'CO2e')); // new promise chain that does all of the work for that country
}
$q.all(promises).then(function(arrayofCountryEmFacPurElecs) {console.log('all countries completed')});

Using protractor with loops

Loop index (i) is not what I'm expecting when I use Protractor within a loop.
Symptoms:
Failed: Index out of bound. Trying to access element at index:'x', but there are only 'x' elements
or
Index is static and always equal to the last value
My code
for (var i = 0; i < MAX; ++i) {
getPromise().then(function() {
someArray[i] // 'i' always takes the value of 'MAX'
})
}
For example:
var expected = ['expect1', 'expect2', 'expect3'];
var els = element.all(by.css('selector'));
for (var i = 0; i < expected.length; ++i) {
els.get(i).getText().then(function(text) {
expect(text).toEqual(expected[i]); // Error: `i` is always 3.
})
}
or
var els = element.all(by.css('selector'));
for (var i = 0; i < 3; ++i) {
els.get(i).getText().then(function(text) {
if (text === 'should click') {
els.get(i).click(); // fails with "Failed: Index out of bound. Trying to access element at index:3, but there are only 3 elements"
}
})
}
or
var els = element.all(by.css('selector'));
els.then(function(rawelements) {
for (var i = 0; i < rawelements.length; ++i) {
rawelements[i].getText().then(function(text) {
if (text === 'should click') {
rawelements[i].click(); // fails with "Failed: Index out of bound. Trying to access element at index:'rawelements.length', but there are only 'rawelements.length' elements"
}
})
}
})
The reason this is happening is because protractor uses promises.
Read https://github.com/angular/protractor/blob/master/docs/control-flow.md
Promises (i.e. element(by...), element.all(by...)) execute their then functions when the underlying value becomes ready. What this means is that all the promises are first scheduled and then the then functions are run as the results become ready.
When you run something like this:
for (var i = 0; i < 3; ++i) {
console.log('1) i is: ', i);
getPromise().then(function() {
console.log('2) i is: ', i);
someArray[i] // 'i' always takes the value of 3
})
}
console.log('* finished looping. i is: ', i);
What happens is that getPromise().then(function() {...}) returns immediately, before the promise is ready and without executing the function inside the then. So first the loop runs through 3 times, scheduling all the getPromise() calls. Then, as the promises resolve, the corresponding thens are run.
The console would look something like this:
1) i is: 0 // schedules first `getPromise()`
1) i is: 1 // schedules second `getPromise()`
1) i is: 2 // schedules third `getPromise()`
* finished looping. i is: 3
2) i is: 3 // first `then` function runs, but i is already 3 now.
2) i is: 3 // second `then` function runs, but i is already 3 now.
2) i is: 3 // third `then` function runs, but i is already 3 now.
So, how do you run protractor in loops?
The general solution is closure. See JavaScript closure inside loops – simple practical example
for (var i = 0; i < 3; ++i) {
console.log('1) i is: ', i);
var func = (function() {
var j = i;
return function() {
console.log('2) j is: ', j);
someArray[j] // 'j' takes the values of 0..2
}
})();
getPromise().then(func);
}
console.log('* finished looping. i is: ', i);
But this is not that nice to read. Fortunately, you can also use protractor functions filter(fn), get(i), first(), last(), and the fact that expect is patched to take promises, to deal with this.
Going back to the examples provided earlier. The first example can be rewritten as:
var expected = ['expect1', 'expect2', 'expect3'];
var els = element.all(by.css('selector'));
for (var i = 0; i < expected.length; ++i) {
expect(els.get(i).getText()).toEqual(expected[i]); // note, the i is no longer in a `then` function and take the correct values.
}
The second and third example can be rewritten as:
var els = element.all(by.css('selector'));
els.filter(function(elem) {
return elem.getText().then(function(text) {
return text === 'should click';
});
}).click();
// note here we first used a 'filter' to select the appropriate elements, and used the fact that actions like `click` can act on an array to click all matching elements. The result is that we can stop using a for loop altogether.
In other words, protractor has many ways to iterate or access element i so that you don't need to use for loops and i. But if you must use for loops and i, you can use the closure solution.
Hank did a great job on answering this.
I wanted to also note another quick and dirty way to handle this. Just move the promise stuff to some external function and pass it the index.
For example if you want to log all the list items on the page at their respective index (from ElementArrayFinder) you could do something like this:
var log_at_index = function (matcher, index) {
return $$(matcher).get(index).getText().then(function (item_txt) {
return console.log('item[' + index + '] = ' + item_txt);
});
};
var css_match = 'li';
it('should log all items found with their index and displayed text', function () {
$$(css_match).count().then(function (total) {
for(var i = 0; i < total; i++)
log_at_index(css_match, i); // move promises to external function
});
});
This comes in handy when you need to do some fast debugging & easy to tweak for your own use.
I am NOT arguing with the logic or wisdom of the far more learned people discussing above. I write to point out that in the current version of Protractor within a function declared as async, a for loop like the below (which I was writing in typeScript, incorporating flowLog from #hetznercloud/protractor-test-helper, though I believe console.log would also work here) acts like what one might naively expect.
let inputFields = await element.all(by.tagName('input'));
let i: number;
flowLog('count = '+ inputFields.length);
for (i=0; i < inputFields.length; i++){
flowLog(i+' '+await inputFields[i].getAttribute('id')+' '+await inputFields[i].getAttribute('value'));
}
producing output like
count = 44
0 7f7ac149-749f-47fd-a871-e989a5bd378e 1
1 7f7ac149-749f-47fd-a871-e989a5bd3781 2
2 7f7ac149-749f-47fd-a871-e989a5bd3782 3
3 7f7ac149-749f-47fd-a871-e989a5bd3783 4
4 7f7ac149-749f-47fd-a871-e989a5bd3784 5
5 7f7ac149-749f-47fd-a871-e989a5bd3785 6
...
42 7f7ac149-749f-47fd-a871-e989a5bd376a 1
43 7f7ac149-749f-47fd-a871-e989a5bd376b 2
As I understand it, the await is key here, forcing the array to be resolved up front (so count is right) and the awaits within the loop cause each promise to be resolved before i is allowed to be incremented.
My intent here is to give readers options, not to question the above.
The easier way for doing this these days
it('test case', async () => {
let elems = element.all(selector)
for (let i=0; i < await elems.count(); i++) {
console.log(await elems.get(i).getText())
}
});

Angular JS with Google Feed API - strange behaviour

In one of my controller functions in my angular.js application, I'm trying to use Google's RSS Feed API to fetch feed entries to populate in my scope.items model.
It's behaving quite strangely, with the console output in the innermost part always being '2' but while the console output in the outermost loop being '0' and '1' (which is correct since that is the length of my items array).
I'm thinking it could have something to do with the Google API thing being an async request and that it hasn't finished before I try to manipulate it (?). Still doesn't make sense that my iterator variable would become '2' though!
Code:
function ItemListCtrl($scope, Item) {
$scope.items = Item.query({}, function() { // using a JSON service
for (var i = 0; i < $scope.items.length; i++) { // the length is 2 here.
$scope.items[i].entries = [];
console.log(i); // gives '0' and '1' as output in the iterations.
var feed = new google.feeds.Feed($scope.items[i].feedUrl);
feed.load(function(result) {
console.log(i); // gives '2' as output in both the iterations.
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
$scope.items[i].entries[j] = result.feed.entries[j];
}
}
});
}
});
}
The callback function is executed asynchonously, after the loop over the items has ended. And at the end of the loop, i is equal to 2, since there are 2 items in your items array.
See Javascript infamous Loop issue? for another example of the same behavior, and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake for an more in-depth explanation.

Flash AS 3 Loader OnComplete Inside a Loop

As a followup to the question, How to get associated URLRequest from Event.COMPLETE fired by URLLoader, how can I make the function work for loader object in a loop?
Here is my existing (rough) code; I always get the mylabel from the last element of the array.
var _loader = new Loader();
for (j = 0; j < 5; j++) {
//mylabel variable is correct setup in the loop
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void {
doneLoad(e, mylabel);
});
_loader.load(new URLRequest(encodeURI(recAC[j].url)));
}//for loop
As per the comments above, this won't work because:
1) You're just adding the same event listener 5 times to the loader.
2) You're just reseting your same loader object 5 times.
The final output will just be as though you only called it the last time.
There are a variety of ways to address this - loading stuff asynchronously is one of the great mindfucks of learning to code - but the simplest way is probably just to create five separate loaders.
I'd do something like this:
var loaders:Array = [];
var labels:Array = ["label1", "label2", "label3", "label4", "label5"];
for (var j:int = 0; j < 5; j++) {
loaders[j] = new Loader();
loaders[j].contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
loaders[j].load(new URLRequest(encodeURI(recAC[j].url)));
}
function completeHandler(e:Event):void {
doneLoad(e.currentTarget, labels[loaders.indexOf(e.currentTarget)]);
}
The confusing part is finding a good way to keep track of which load is associated with which label etc, since in theory your loads can finish in any order. That's why I've got a separate label array there, and then you just match up the desired label with the loader that just finished loading.
I hope that helps!
the line belove should work but it returns -1, always.
loaders.indexOf(e.currentTarget);
Here my code
for(i; i < total; i++){
imgLoaderArray[i] = new Loader();
imgLoaderArray[i].contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, urlError);
imgLoaderArray[i].contentLoaderInfo.addEventListener(Event.COMPLETE, loaded);
imgLoaderArray[i].load(new URLRequest(xmlList[i].image));
}
function loaded(e:Event):void{
trace("index: "+imgLoaderArray.indexOf(e.currentTarget)); // return -1 every time
}

Resources