Whenever I use a method on WebDriverIO's browser object, it completely breaks out of whatever loop I'm in.
The debugger confirms this. It doesn't execute any other code in the rest of this loop. The loop should iterate 51 times which it does. But it never executes the rest of the code after browser.methods()
Here is the code:
await redirects.map(async (obj) => {
iterator7 += 1;
console.log('In map method...');
giveMeURLString = await browser.url(obj.Origin); //Why does this return / reloop????
// Also why is giveMeURLString populated with undefined
iterator8 += 1;
console.log('I should be able to see this...')
})
You can also run the test yourself here:
https://github.com/DavidMLink/WebDriverIOBug
Why is it prematurely exiting my loops?
It was a problem with Async/Await. Using a simple ""synchronous loop"". Any type of for loop like "for of" or "for in" or "for" will do the trick.
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 realized when I do something like this:
for (const entity of someArr) {
console.log('start now!')
await doSomeAsycAction()
console.log('waited X secs!')
}
It prints out:
start now!
waited X secs!
start now!
waited X secs!
...
But when I use map:
arr.map(async entity => {
console.log('start now!')
await doSomeAsycAction()
console.log('waited X secs!')
})
It prints out:
start now!
start now!
start now!
...
waited X secs!
waited X secs!
waited X secs!
...
Can someone explain why this is the case?
The difference between the two flows is that the first one (using for, for..in or for..of) is running the loop iterations sequentially and the other one (using map, filter, reduce, forEach and so on) are running (in case async symbol is used somewhere in the mapping function) concurrently (kind of).
In for loops, the next iteration must wait for the previous one to finish. This allows you to do some async operations relevant for the next iteration.
In contrast, using the async methods runs each iteration independently, so you can't rely on other iterations in your current iteration. Those kind of functions receive a function as an argument and executes it immediately for every item in the array.
Think of every iteration as an independent promise execution. When running an async function, the await symbol tells that this operation might take a while (i.e I/O, DB calls, network operations...) and let's the code outside of the current executed function keep going (and resume later on, after the async call returns). The map function sees that the current iteration is busy and go on to the next one. Somewhen in the future it would resume and execute console.log('waited X secs!').
You can simulate the same behavior of async executions with for loop this way (would maybe help demonstrating the difference):
for (const entity of someArr) {
(async () => {
console.log('start now!')
await doSomeAsycAction()
console.log('waited X secs!')
})()
}
The async-await syntax is working per function scope, and map is defining a new function scope (the function passed as the param to the function) just like the (anonymous) function that gets executed each iteration in my example. Wish it helps to understand.
One important thing to notice is that each iteration of the map doesn't return the mapped value you were expecting for, but a promise that will be resolved with this value. So if you try to rely on one of the mapped array values - you must add an await right before it, otherwise the value type would still be a promise. Take a look at the following example:
let arr = [1];
arr = arr.map(async entity => incrementAsync(entity));
console.log(arr[0]) // would print an unresolved Promise object
console.log(await arr[0]) // would print 2
Basic array prototype loop functions like forEach, map, filter, find etc. don't wait for next iteration.Their basic behavior is to iterate not awaiting. If you want use like waiting function then try to use like below
for (const event of events) {
if (failure or conditional) {
continue;
}
}
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();
So there are some ways to stopping a Generator in for of loop, but how does break send a signal to the Generator(in comparison with return in for-of)?
please consider the code.
As an example, the preceding code just increases a value from 1 to 10 ,and do pause and resume in between.
function *Generator() {
try {
var nextValue;
while (true) {
if (nextValue === undefined) {
nextValue = 1;
}
else {
nextValue++;
}
yield nextValue;
}
}
// cleanup clause
finally {
console.log( "finally has been reached." );
}
}
it loops over it 10 times by using for of:
var it = Generator();// it gets Generator's iterator
for (var v of it) {
console.log(v);
if (v > 9) {
//it.return("stop");//this is another tool for stopping, but it doesn't stop immediately.
break;
console.log("it won't run");//this line won't run
}
}
When it.return() is used by the way, the implementation's clear(it is the main Object and has got the control, but what about the break?);
Iterable objects like your it generator object have a property with the key Symbol.iterator that is a function returning an iterator. Iterators are required to have a .next() method to advance from one item to the next. Then can also optionally have a .return() method, which is called when you break, return, or throw, causing the for..of to stop before it runs to completion. So in your case, break; will automatically call it.return().
The other side of it is that on ES6 generator, .next() makes it resume execution at the currently paused yield, and .return() makes it act like the yield is a return statement, so break inside the loop causes yield nextValue; to behave like return;, which will exit the generator and trigger the finally block.
how does break send a signal to the Generator?
The loop will call the IteratorClose operation, which basically amounts to invoking the iterator's .return() method with no arguments if the iterator object has such a method - which generators do.
This also happens when a throw or return statement in the loop body is evaluated.
When it.return() is used by the way, the implementation is clear
…but horrible. As you found out, it doesn't stop immediately. That's because a method call just advances the generator and gets you some result back from it, but is has nothing to do with your loop. The loop body will just continue to be executed until the loop condition is evaluated again, which then will check the iterator and notice that it's already done.
Does the ForEach loop allow us to use break and continue?
I've tried using both but I received an error:
Illegal break/continue statement
If it does allow, how do I use them?
No, it doesn't, because you pass a callback as a return, which is executed as an ordinary function.
Let me be clear:
var arr = [1,2,3];
arr.forEach(function(i) {
console.log(i);
});
// is like
var cb = function(i) {
console.log(i);
// would "break" here do anything?
// would "continue" here do anything?
// of course not.
}
for(var j = 0; j < arr.length; j++) {
cb(arr[j]);
}
All forEach does is call a real, actual function you give to it repeatedly, ignore how it exits, then calls it again on the next element.
In that callback function if you return it will incidentally work like continue in the case of forEach. Because the rest of the function won't execute that time, and it will be called again. There is no analogue of break.
Ruby supports this flexibility, using blocks/procs instead of methods/lambdas.
Per Mozilla's documentation:
Note : There is no way to stop or break a forEach loop. The solution is to use Array.every or Array.some. See example below.
every and some are exactly like forEach, except they pay attention to the return value of the callback. every will break on a falsey value and some will break on a truthy value, since they shortcircuit. They are exactly analogous to && and ||. && tests whether every expression is truthy, || tests for whether some is, which should help you remember how short-circuiting works with every and some. When in doubt, try it.
As already answered, you cannot use continue or break inside a JavaScript Array.prototype.forEach loop. However, there are other options:
Option 1
Use the jQuery .each() function (Reference).
Simply return true to continue, or return false to break the loop.
Option 2
Just use return to continue or throw new Error() to break. I don't necessarily recommend doing it this way, but it's interesting to know that this is possible.
try {
[1, 2, 3, 4].forEach(function(i) {
if (i === 2) {
return; // continue
}
if (i === 3) {
throw new Error(); // break
}
console.log(i);
});
}
catch (e) {
}
The expected result is just 1