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();
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 have been building a React app that uses iteration a lot. I am using JSLint and get the annoying warning saying:
Don't make functions within a loop
On the following loop:
if(currentSearch[i].users.length > 0) {
var matched = false;
//I hate how JSLint just can't handle setting a variable to true within a function
//this is loop where I get the warning
currentSearch[i].users.some(childUser => {
if(childUser.id === user.id) {
return matched = true;
}
})
//^^^^^ Warning
if(!matched) {
var alert = new AlertObject(user,currentSearch[i],true,false);
alerts.push(alert);
}
}
I don't think I set a function in the loop? I am using the array.some function which will break the loop if I return true, which is what I do. I return a variable, declared outside of the loop, as true. This breaks me out of the loop, and allows me to do logic below.
I should also be noted that this is also entirely within a loop, as we are iterating over current search users. I get no runtime or compile errors, and this code works fine, but maybe I am setting myself up for disaster in the future.
Any ideas why I am getting this error? And if I am missing some best practice?
Since in the first line you reference currentSearch[i], because the [i] I assume the whole block of code you pasted here is inside some kind of loop, probably a for.
Then, you are creating a function for the Array.some callback, which triggers the error.
One solution would be to move that callback declaration to be outside the parent loop, but since you are using a variable from the scope, it will require some refactor.
Posible solution
You can declare a function outside the parent loop (the one outside the code you provided here) that checks for the child user.
//Please provide a better name for the function according to the context.
const checkChildUser = function (childUser) {
return this.id === childUser.id;
};
And then pass it to the Array.some function you are using:
currentSearch[i].users.some(checkChildUser, user);
I'm not familiar with React, but this looks like an ES6 arrow function:
childUser => { ... }
Which would be the equivalent of
function (childUser) { ... }
The problem here is that you create a function that modifies the matched variable. This variable is declared with var, so it's scope is the whole function, not a single loop iteration. This could lead to surprising results, since functions created in each iteration will actually refer to the same variable.
Simply using the value returned by some() instead of changing matched in the callback (as suggested by Yury Tarabanko) should remove the warning.
In your snippet, the Don't make functions within a loop warning is not caused by the if statement, but by this following anonymous function:
childUser => {
if(childUser.id === user.id) {
return matched = true;
}
}
Since you've said that the entire code is within a loop, a new instance of that anonymous function is being created for every iteration. This will affect the performance.
Well, you are providing a function in your .some() as a parameter so thats what triggers the warning.
The reason ESLint warns against it
Writing functions within loops tends to result in errors due to the way the function creates a closure around the loop - source
You can do it like this
function compareId(childUser) {
if (childUser.id === user.id) {
return true;
}
}
if (currentSearch[i].users.length > 0) {
var matched = currentSearch[i].users.some(compareId);
if (!matched) {
var alert = new AlertObject(user, currentSearch[i], true, false);
alerts.push(alert);
}
}
var a = 1;
function myFunction() {
++a;
return true;
}
// Alert pops up.
if (myFunction() && a === 2) {
alert("Hello, world!");
}
// Alert does not pop up.
if (a === 3 && myFunction()) {
alert("Hello, universe!");
}
https://jsfiddle.net/3oda22e4/6/
myFunction increments a variable and returns something. If I use a function like that in an if statement that contains the variable which it increments, the condition would be order-dependent.
Is it good or bad practice to do this, and why?
Conditions are order-dependent whether you change the variables used in the condition or not. The two if statements that you used as an example are different and will be different whether you use myFunction() or not. They are equivalent to:
if (myFunction()) {
if (a === 2) {
alert("Hello, world!")
}
}
// Alert does not pop up.
if (a === 3) {
if (myFunction()) {
alert("Hello, universe!")
}
}
In my opinion, the bad practice in your code is not the fact that you change the condition's operands value inside the condition, but the fact that your application state is exposed and manipulated inside a function that does not even accept this state changing variable as a parameter. We usually try to isolate the functions from the code outside their scope and use their return value to affect the rest of the code. Global variables are 90% of the time a bad idea and as your code base gets larger and larger they tend to create problems that are difficult to trace, debug and solve.
It's bad practice, for the following reasons:
The code is far less readable than well-constructed code. This is very important if the code is later examined by a third party.
If myfunction is changed later, the code flow is completely unpredictable, and might require a major documentation update.
Small and simple changes can have drastic effects on the execution of the code.
It looks amateur.
If you have to ask, it's hardly a good practice. Yes, it's a bad practice for exactly the reason you mentioned: changing the order of operands of a logical operation should not affect the outcome, and therefore side effects in conditions should generally be avoided. Especially when they are hidden in a function.
Whether the function is pure (only reads state and does some logic) or whether it mutates state should be obvious from its name. You have several options to fix this code:
put the function call before the if:
function tryChangeA() {
a++;
return true;
}
var ok = tryChangeA();
if (ok && a == 2) … // alternatively: if (a == 2 && ok)
make the mutation explicit inside the if:
function testSomething(val) {
return true;
}
if (testSomething(++a) && a == 2) …
put the logic inside the called function:
function changeAndTest() {
a++;
return a == 2;
}
if (changeAndTest()) …
MyFunction violates a principle called Tell, Don't Ask.
MyFunction changes the state of something, thus making it a command. If MyFunction succeeds or somehow fails to increment a, it shouldn't return true or false. It was given a job and it must either try to succeed or if it finds that job is impossible at the moment, it should throw an exception.
In the predicate of an if statement, MyFunction is used as a query.
Generally speaking, queries should not exhibit side-effects (i.e. not changing things that can be observed). A good query can be treated like a calculation in that for the same inputs, it should produce the same outputs (sometimes described as being "idempotent").
It's also important to know that these are guidelines to help you and others reason about the code. Code that can cause confusion, will. Confusion about code is a hatchery for bugs.
There are good patterns like the Trier-Doer pattern which can be used like your code example, but everyone reading it must understand what's happening though names and structure.
The code presents more then one bad practice actually:
var a = 1;
function myFunction() {
++a; // 1
return true;
}
if (myFunction() && a === 2) { // 2, 3, 4
alert("Hello, world!")
}
if (a === 3 && myFunction()) { // 2, 3, 4
alert("Hello, universe!")
}
Mutates a variable in a different scope. This may or may not be a problem, but usually it is.
Calls a function inside an if statement condition.
This do not cause problems in itself, but it's not really clean.
It's a better practice to assign the result of that function to a variable, possibly with a descriptive name. This will help whoever reads the code to understand what exactly you want to check inside that if statement. By the way, the function always return true.
Uses some magic numbers.
Imagine someone else reading that code, and it is part of a large codebase. What those numbers mean? A better solution would be to replace them with well named constants.
If you want to support more messages, you need to add more conditions.
A better approach would be to make this configurable.
I would rewrite the code as follows:
const ALERT_CONDITIONS = { // 4
WORLD_MENACE: 2,
UNIVERSE_MENACE: 3,
};
const alertsList = [
{
message: 'Hello world',
condition: ALERT_CONDITIONS.WORLD_MENACE,
},
{
message: 'Hello universe',
condition: ALERT_CONDITIONS.UNIVERSE_MENACE,
},
];
class AlertManager {
constructor(config, defaultMessage) {
this.counter = 0; // 1
this.config = config; // 2
this.defaultMessage = defaultMessage;
}
incrementCounter() {
this.counter++;
}
showAlert() {
this.incrementCounter();
let customMessageBroadcasted = false;
this.config.forEach(entry => { //2
if (entry.condition === this.counter) {
console.log(entry.message);
customMessageBroadcasted = true; // 3
}
});
if (!customMessageBroadcasted) {
console.log(this.defaultMessage)
}
}
}
const alertManager = new AlertManager(alertsList, 'Nothing to alert');
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
alertManager.showAlert();
A class with a precise function, that use its own internal state, instead of a bunch of functions that rely on some variable that could be located anywhere. Whether to use a class or not, it's a matter of choice. It could be done in a different way.
Uses a configuration. That means that would you want to add more messages, you don't have to touch the code at all. For example, imagine that configuration coming from a database.
As you may notice, this mutates a variable in the outer scope of the function, but in this case it does not cause any issue.
Uses constants with a clear name. (well, it could be better, but bear with me given the example).
A function that changes stuff. What is the world coming too? This function must change stuff and return different values each time its called.
Consider the dealCard function for a deck of playing cards. it deals the cards 1-52. Each time it is called it should return a different value.
function dealCard() {
++a;
return cards(a);
}
/* we'll just assume the array cards is shuffled */
/* for the sake of brevity we'll assume the deck is infinite and doesn't loop at 52*/
I am trying to create a FOR loop that removes an element every 1000ms instead of rushing instantaneously through the array and perform the operations.
I am doing this for reasons of performance since going normally through the loop freezes my UI.
function removeFunction (i,selected) {
selected[i].remove();
}
function startLoop() {
var selected = paper.project.selectedItems;
for (var i = 0; i < selected.length; i++) {
if (selected[i].name === "boundingBoxRect") continue;
setTimeout(removeFunction(i,selected),1000)
}
}
It seems that the selected[i].remove() method is getting called without any delay. Why is that? Since I have set a Timeout of 1000ms shouldn't the items get removed with 1000ms interval between each?
Note
In the code above, I am skipping an item called boundingBoxRect since I don't want to remove that. Just stating this so there is no confusion
Simply turn it into a recursive function:
function removeFunction (i, selected) {
// If i is equal to the array length, prevent further execution
if (i === selected.length)
return;
// Remove ith child of selected array
selected[i].remove();
// Trigger same function after 1 second, incrementing i
setTimeout(function() {
removeFunction(++i,selected)
}, 1000);
}
// Trigger the function to begin with, passing in 0 as i
removeFunction(0, paper.project.selectedItems);
Here's a JSFiddle demo (using console.log instead of selected[i].remove(), as you haven't provided a definition for that function);
It seems that the selected[i].remove() method is getting called without any delay. Why is that?
Because that's what you're telling it to do. setTimeout(removeFunction(i,selected),1000) calls removeFunction immediately and passes its return value into setTimeout, exactly the way foo(bar()) calls bar and passes its return value into foo.
You can get the effect you want by using a builder function:
setTimeout(buildRemover(i,selected),1000);
...where buildRemover is:
function buildRemover(index, array) {
return function() {
removeFunction(index, array);
};
}
Note how buildRemover creates a function that closes over the index and array variables. Then it returns a reference to that function, which is what gets scheduled via setTimeout. When the timeout occurs, that generated function is called, and it calls removeFunction with the appropriate values.
You can also do something similar using ES5's Function#bind:
setTimeout(removeFunction.bind(null, i, selected),1000);
Function#bind returns a new function that, when called, will call the original (removeFunction above) use the given this value (null in our example) and arguments.
I have two asynchronous functions the one nested in the other like this:
//Async 1
document.addEventListener("click", function(event){
for (var i in event){
//Async 2
setTimeout(function(){
console.log(i);
}, 200*i);
}
});
What I want is to be able and print each entry(i) of the event object. The output on Firefox is however this:
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
MOZ_SOURCE_KEYBOARD
..
If I move console.log(i) outside Async 2 then I get the correct result:
type
target
currentTarget
eventPhase
bubbles
cancelable
..
Why doesn't it work correctly when reading the i inside async 2? Shouldn't event be "alive" inside the whole Async 2 block of code as well?
setTimeout uses i as it appears in async1. That is, it references i instead of using the value of i when the timeout function is created. When your timeout function finally runs, it looks at the current value of i, which is the last key in event after the for-loop.
You can avoid this by using a self-calling function, such that the arguments of that function are local to the timeout function:
for (var i in event) {
setTimeout((function(i) {
return function() { console.log(i); }
})(i), 200 * i);
}
Use this:
document.addEventListener("click", function(event){
var count = 1;
for (var i in event){
//Async 2
setTimeout((function(i){
return function () {
console.log(i);
};
})(i), 200 * count++);
}
});
DEMO: http://jsfiddle.net/AQykp/
I'm not exactly sure what you were going for with 200*i, since i is a string (not even digits). But I tried to fix it with counter in my answer, assuming what you really wanted.
So the reason you were seeing the results you were was because of the common closure problem. The setTimeout callback will execute after the for loop completes, leaving i as the last key in event. In order to overcome that, you have to add a closure that captures the value of i at that point in the loop.
Event is indeed alive inside your callback.
The problem is that, by the time your function is executed, the value of i has changed (from what I can see from the output, the loop has ended and i has reached its maximum value) and thus outputting the same value for every callback.
If you use the function Ian commented, you will curry the actual value into a new function. That way the value of i can vary, you captured the current value inside the inner function