This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I am trying to call a promise inside a for loop. I need the call to have the relevant loop item. I read a bit about it but still can't seem to figure the correct way to do it.
var params;
var identifylayers =["parcels", "lots", "gasstation"];
for (i = 0; i < identifylayers.length; i++)
{
lname = identifylayers[i];
govmap.intersectFeatures()
.then(function (e)
{
alert( lname);
});
}
Running the function returns: gasstation,gasstation,gasstation
I want it to return: parcels, lots, gasstation
What am I doing wrong?
Its the async devil of JavaScript lurking in your code. :D
Observe it like this -
As soon as your program flow encounter a promise call which here is - intersectFeatures().then(fn (){}), it know that this call will eventually finish so the program flow will never attempt to execute the callback passed to .then() right at the moment instead it will keep it safe with itself and set a reminder that as soon as the intersectFeatures is finished it need to execute the callback passed to .then().
Next thing to notice is how the .then(callback) will have access to lname. Its by closures. So
Another important thing to notice is that this whole stuff is inside a loop. Your loop has no idea whether the code its iterating on is synchronous or asynchronous. Its only task is to iterate. And as we mentioned before in Point - 1 the .then(callback) will not be executed right at the moment. So by the time then .then(callback) will be executed your loop would already be finished and at the end of iteration lname holds gasstation. Hence your .then(callback) only print gasstation
Solution:
This is really old JS solution of iterating the async code and still ensuring the proper variable access using closures -
for (i = 0; i < identifylayers.length; i++)
{
lname = identifylayers[i];
(function (lname) {
govmap.intersectFeatures()
.then( function () {
alert(lname);
} )
})(lname);
}
This utilises the benefit of functional scope of a variable and idea of closures. Google it to find more information.
Its also referred to as befriending closures, I just made that up :D
Cheers!
Define lname in the loop. Currently it is the same instance for each iteration, so it gets overriden, and then the promise resolves and prints the last overwrite each time.
const lname = identifylayers[i];
It is getting overriden before the async code runs.
async function intersectFeatures() {
return "OK"
}
var params;
var identifylayers = ["parcels", "lots", "gasstation"];
for (let i = 0; i < identifylayers.length; i++) {
const lname = identifylayers[i];
intersectFeatures()
.then(function(e) {
alert(lname);
});
}
Related
Trouble finding the reason why JavaScript for loop is not executing. Wrote 2 simple functions below that I want to execute and run i.e.: Bought method should try to "simulate" synchronous code.
The problem is that for some reason the for loop in the addNodes() method is never executed. However, if I run this separately i.e. for example line by line
var result = [];
var addressBookNodes = await generateAddressBooksNodes();
addNodes(result, addressBookNodes);
that tells me that the code is running fine however most likely it has something to do with the asynchronous nature of the generateAddressBooksNodes method. If I simply run the command :
var addressBookNodes = await generateAddressBooksNodes();
in the browser, I get an array of objects exactly what I was expecting to get. Basically the generateAddressBooksNodes returns a promise and when that promise is resolved I can see the correct array returned however what I do not understand why the for loop is not executed if the nodes objects have at least one element as shown in the picture below.
function addNodes(result, nodes){
console.log("3");
console.log(nodes);
for (var num in nodes) {
console.log("4");
let singleNode = nodes[num];
console.log(singleNode);
console.log("5");
result.push(singleNode);
}
}
async function getAddressBookAndContactNodes() {
var result = [];
console.log("1");
var addressBookNodesPromise = generateAddressBooksNodes();
addressBookNodesPromise.then( (arrayOfAddressBookNodes) => {
console.log("2");
addNodes(result, arrayOfAddressBookNodes);
})
return result;
}
Update 26 August 2020 :
After poking around the "arrayOfAddressBookNodes" object i noticed something really strange. I added additional print statement and printed the length of the "arrayOfAddressBookNodes" array. The length of the array is 0 when runned in the function. I do not understand how the length can be 0 if the object is printed shortly before the for loop and as shown on the picture below the length there is :1. What the hell is going on here?
I found another article i.e. JavaScript Array.length returning 0 that is basically explaining this. And in one of the commends it has been mentioned to use Map instead of an Array. I decided to use Set, and still get the same error i.e. the size of the set is 0 although the Set contains an object. i.e. below is the code and the picture of that execution.
async function getAddressBookAndContactNodes() {
var result = new Set();
console.log("1");
var addressBookNodes = await generateAddressBooksNodes();
console.log("2");
console.log(addressBookNodes);
console.log("3");
console.log("addressBookNodes size : " + addressBookNodes.size);
addressBookNodes.forEach(function(value) {
result.add(value);
});
console.log("6");
console.log(result);
console.log("7");
return result;
}
example using set
all this is really confusing to someone having a c++ backgroud, it makes my head explode.
Update 2 : 26 August 2020.
Ok i solved my problem. The problem was that the the promises are not working withing the for loop everything is explained here.
i need to use the regular "for (index = 0; index < contactsArray.length; ++index) " instead of foreach. after that it all worked. Somehow this leaves the impression that the tools of the language are broken in so many ways.
If generateAddressBooksNodes is returning a promise, you can use async to wait for the results:
async function getAddressBookAndContactNodes() {
var result = [];
console.log("1");
var addressBookNodesPromise = await generateAddressBooksNodes();
// note await here. Also, unless you're using this at a later time in your code, you can save space by not assigning it to a variable and simply returning generateAddressBooksNodes
addNodes(result, arrayOfAddressBookNodes);
return result;
}
I constantly run into problems with this pattern with callbacks inside loops:
while(input.notEnd()) {
input.next();
checkInput(input, (success) => {
if (success) {
console.log(`Input ${input.combo} works!`);
}
});
}
The goal here is to check every possible value of input, and display the ones that pass an asynchronous test after confirmed. Assume the checkInput function performs this test, returning a boolean pass/fail, and is part of an external library and can't be modified.
Let's say input cycles through all combinations of a multi-code electronic jewelry safe, with .next incrementing the combination, .combo reading out the current combination, and checkInput asynchronously checking if the combination is correct. The correct combinations are 05-30-96, 18-22-09, 59-62-53, 68-82-01 are 85-55-85. What you'd expect to see as output is something like this:
Input 05-30-96 works!
Input 18-22-09 works!
Input 59-62-53 works!
Input 68-82-01 works!
Input 85-55-85 works!
Instead, because by the time the callback is called, input has already advanced an indeterminate amount of times, and the loop has likely already terminated, you're likely to see something like the following:
Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
Input 99-99-99 works!
If the loop has terminated, at least it will be obvious something is wrong. If the checkInput function is particularly fast, or the loop particularly slow, you might get random outputs depending on where input happens to be at the moment the callback checks it.
This is a ridiculously difficult bug to track down if you find your output is completely random, and the hint for me tends to be that you always get the expected number of outputs, they're just wrong.
This is usually when I make up some convoluted solution to try to preserve or pass along the inputs, which works if there is a small number of them, but really doesn't when you have billions of inputs, of which a very small number are successful (hint, hint, combination locks are actually a great example here).
Is there a general purpose solution here, to pass the values into the callback as they were when the function with the callback first evaluated them?
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!`);
}
});
})();
}
You could use the new feature async await for asynchronous calls, this would let you wait for the checkInput method to finish when inside the loop.
You can read more about async await here
I believe the snippet below achieves what you are after, I created a MockInput function that should mock the behaviour of your input. Note the Async and await keywords in the doAsyncThing method and keep an eye on the console when running it.
Hope this clarifies things.
function MockInput() {
this.currentIndex = 0;
this.list = ["05-30-96", "18-22-09", "59-62-53", "68-82-0", "85-55-85"];
this.notEnd = function(){
return this.currentIndex <= 4;
};
this.next = function(){
this.currentIndex++;
};
this.combo = function(){
return this.list[this.currentIndex];
}
}
function checkInput(input){
return new Promise(resolve => {
setTimeout(()=> {
var isValid = input.currentIndex % 2 > 0; // 'random' true or false
resolve( `Input ${input.currentIndex} - ${input.combo()} ${isValid ? 'works!' : 'did not work'}`);
}, 1000);
});
}
async function doAsyncThing(){
var input = new MockInput();
while(input.notEnd()) {
var result = await checkInput(input);
console.log(result);
input.next();
}
console.log('Done!');
}
doAsyncThing();
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am seeking help understanding why the way I am using anonymous functions are erroring in some circumstances.
In the snippet below I popuplate 2 arrays with functions which are invoked at a later stage.
var arr1 = [];
var arr2 = [];
// callback function
var func = function(i){
return function(){$("body").append(i);}
};
var i = 1;
while(i <= 5)
{
arr1.push(function(){$("body").append(i);});
arr2.push(func(i));
i++;
}
// invoke functions
$("body").append("Output from arr1 == ");
for(var c = 0; c < arr1.length; c++){ arr1[c](); }
$("body").append("<br> Output from arr2 == ");
for(var c = 0; c < arr1.length; c++){ arr2[c](); }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Now, I think I understand why arr1 outputs 66666, because at the time of invocation, i == 6 and becuase it has not been stored as a param within that function at time of creation, i retains it's last known value?
What I really don't understand is why i recieve TypeError: arr2[c] is not a function When I change the callback function to:
var func = function(i){
$("body").append(i);
};
Why does this happen and is this the most appropriate / elegant way to achieve this functionality.
For the first part of the question, you are right, it will retain the latest known value. To avoid that, you need to use closure. For example:
(function(x) {
arr2.push(func(x));
})(i);
Closure can often be used as a "scope hack". In this case, it will only ensure that the injected value is the constant current value of i.
For the second part of the question, your confusion is probably caused by the fact that you insert the result of the function (i.e.: func(i)) instead of the function itself (func).
In the first scenario, you return an explicit function(){...} so your array will contain a function that you can call with operator "()".
In the second scenario, in which you return $(<...>).append(), it's not guaranteed anymore that your return value is a function. In that case, the result is "jQuery" (see: jQuery append) and not a function, hence the error is correct. You cannot use the value as a function.
One way out is:
arr2.push(func);
Then:
arr2[c](c);
Or probably this:
(function(x){
arr2[x](x);
})(c);
If needed, it can be helpful to always console.log what is populated inside your arrays before assuming it right. Since Javascript is not a typed language, you may go for a little while before suspecting anything wrong as it will seem work as intended.
Hope this helps!
The other day, I wrote this function in JavaScript as a simple DTO for an object from a jQuery plugin. I assumed that if I wrote a general return gridColumns line at the end of the function, the array could possibly be returned before my for loop was finished populating it. So I wrote this while (true) statement at the end, thinking I was being clever.
function getGridColumns() {
var gridColumns = [];
var records = $("#jqxGrid").jqxGrid("columns").records;
for (var i = 0; i < (records.length); i++) {
var obj = {
datafield: records[i].datafield,
width: records[i].width,
cellsalign: records[i].cellsalign,
hidden: records[i].hidden
}
gridColumns.push(obj);
}
while (true) {
if (gridColumns.length == records.length {
return gridColumns;
}
}
};
A buddy of mine looked at my little "hack" and said it was completely unnecessary, and I did some testing and determined that he's right.
So, here's where I'm at. How is JavaScript asynchronous, and how is it not? Can anyone help me understand this paradigm so that I can write better JavaScript?
The most common mechanisms that spawn new "threads" of execution and thus so to speak introduce an asynchronous situation in javascript are AJAX call callbacks (unless the call was specifically made synchrounous) and setInterval() and setTimeout() calls. As pointed out by Pointy, there are more than this though.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
javascript for loop unexpected behaviour
I'am programming in node.js and i run into a small problem what is caused by some strange pass by reference problem.
i use the following code:
for(j in groups){
console.log(j, somefunction(groups[j]))
}
inside of the function some funky things are done with the data but afterwards i get the following as result:
3 data
3 data
3 data
3 data
3 data
3 data
instead of
1 data
2 data
3 data
4 data
5 data
6 data
but it keeps the correct amount of result..
Your quoted code doesn't do what you've listed, you probably lost something simplifying it for the question.
This code would have the effect you're describing, though:
for(j in groups){
doSomething(function() {
// Callback for `doSomething`
console.log(j, somefunction(groups[j]));
});
}
Note that now I'm creating a function (a callback for whatever doSomething does), not just calling one.
The reason that happens is that the function being created has an enduring reference to the j variable (and everything else in the execution context), not a copy of it as of when the function was created. It's called a closure over that execution context. So your loop runs, a series of calls to doSomething start a series of things, and then later when the callbacks are called, j is 3 and so it's the same for all of them.
The usual solution is to create functions that close over something that won't change before they get called, like this:
for(j in groups){
doSomething(makeCallback(j));
}
function makeCallback(jarg) {
return function() {
console.log(jarg, somefunction(groups[jarg]));
};
}
Now, we're using jarg in our callback, rather than j. Because jarg is part of the context of the call to makeCallback, it doesn't change between when we create the function and when it's called later.
Another way you can bind data to functions is to use Function#bind (which effectively creates a closure in the background), which is an ES5 feature available in NodeJS because the V8 engine has it. Here's how that would look:
for(j in groups){
doSomething(function(jarg) {
// Callback for `doSomething`
console.log(jarg, somefunction(groups[jarg]));
}.bind(this, j)); // <== Note the bind
}
or less confusingly (and quite possibly more efficiently):
for(j in groups){
doSomething(handler.bind(this, j));
}
function handler(jarg) {
// Callback for `doSomething`
console.log(jarg, somefunction(groups[jarg]));
}
More about closures:
Closures are not complicated
Do it this way :-
for(var j in groups)
{
//Your code
}
j is not returning the correct value because you have assigned j at a global scope. Hope this help!
ive found if i use
var j = 0
for(i in groups){
j = i
the system does work how but it's not the best awnser