I need some help with the following loop:
localStorage.removeItem('DDFound');
cy.get(sel).each(($el, index, $list) => {
if(localStorage.getItem('DDFound')!='1')
{
cy.log(localStorage.getItem('DDFound'));
cy.wrap($el).click().then(()=> {
cy.get(table).then(elx => {
if(elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(lable).should('contain.text',"Item")
localStorage.setItem('DDFound','1');
}
})
});
}
});
I would like to break just after finding the right item(tableitem) and for that, I'm setting a localstorage (didn't find any other way) but it looks like cypress runs all items in each loop in parallel and not getting to the if(localStorage.getItem('DDFound')!='1') after each element.
You can stop the .each() loop early by returning false in the callback function. In your case, we can just return the value if DDItem does not equal one.
localStorage.removeItem('DDFound');
cy.get(sel).each(($el, index, $list) => {
if(localStorage.getItem('DDFound')!='1')
{
cy.log(localStorage.getItem('DDFound'));
cy.wrap($el).click().then(()=> {
cy.get(table).then(elx => {
if(elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(lable).should('contain.text',"Item")
localStorage.setItem('DDFound','1');
}
})
});
}
return localStorage.getItem('DDFound') !== 1
});
If you have complex code inside the .each() you should wrap it in a promise
let found = false;
cy.get(sel).each(($el, index, $list) => {
if (found) return false; // early exit
return new Cypress.Promise(resolve => {
cy.wrap($el).click().then(() => {
cy.get(table).then(elx => {
if (elx.find(tableitem).length > 0) {
cy.get(tableitem).click();
cy.get(label).should('contain.text',"Item")
found = true;
}
resolve() // wait for above to complete
})
})
})
})
See .each() - Promises
Promises are awaited
If your callback function returns a Promise, it will be awaited before iterating over the next element in the collection.
Related
To avoid callback hell, I am chaining several (Bluebird Promise) instructions, each running an asynchronous for loop. Instead of waiting for each for loop to finish, the chain rushes right to the end where it shows "DONE" while the for loops are still running. How can I change my for loops so that the promise chain "waits" for each one to finish before executing the next "then" section?
return Object1.Asyncmethod1(param1)
.then(function(result1) {
var promiseFor = Promise.method(function(condition, action, value) {
if (!condition(value)) return value;
return action(value).then(promiseFor.bind(null, condition, action));
});
promiseFor(function(count) {
return count < result1.length;
}, function(count) {
return Object.someOtherAsyncAction(someParam)
.then(function(res) {
return ++count;
});
}, 0)
}).then(function(result2) {
//another for loop just like the one above
}).then(function(result3) {
console.log("DONE");
res.json({
result: result3
});
}).catch(function(err) {
res.json({
result: 'error:' + err
});
});
You do not return the Promise created by promiseFor. As of that, the chain is broken, and the .then(function(result2) { does not wait for that code to be finished. You need to add a return in front of the promiseFor(function(count) {
.then(function(result1) {
var promiseFor = Promise.method(function(condition, action, value) {
if (!condition(value)) return value;
return action(value).then(promiseFor.bind(null, condition, action));
});
return promiseFor(function(count) {
return count < result1.length;
}, function(count) {
return Object.someOtherAsyncAction(someParam)
.then(function(res) {
return ++count;
});
}, 0)
})
I am fairly new to JavaScript and Protractor. I have simple task in my test that I am unable to complete.
Check on available Tab on web page.
Check if element is visible on web page.
a) If Yes, return deffered.fullfil(true)
b) If No,
- Click on In-Progress Tab
- Click on Available Tab.
- Go to Step 1.
I am trying to do this recursively and below is my code. It is printing Element found but never exits the function after that and times out.
var check_availability = function(counter, totalCount, element){
var deferred = potractor.promise.defer()
if(counter <= totalCount){
browser.wait(function(){
browser.wait(EC.visibilityOf(element),2000)
return element
}).then(function(success){
console.log('Element found.')
return deferred.fulfill(true)
}, function(err){
inprogressTab.click()
.then(() => availableTab .click())
.then(() => check_availability (counter+1 , totalCount, element))
})
} else{
return deferred.reject(false)
}
return deferred.promise
}
PS: This is a sample code that I am using, corrected some spelling mistakes and syntax.
I see several syntax errors on your shared code. Below i tried to fix those errors and provided the expected behavior but still don't know from where the inprogressTab is coming from.
const check_availibility = function(counter, totalCount, element) {
const deferred = protractor.promise.defer();
if (counter <= totalCount) {
browser
.wait(() => browser.wait(EC.visibilityOf(element), 2000))
.then(
element => {
console.log("Element found.");
return deferred.fulfill(true);
},
err => {
inprogressTab
.click()
.then(() => availableTab.click())
.then(() => check_availibility(counter + 1, totalCount, element));
}
);
} else {
return deferred.reject(false);
}
return deferred.promise;
};
First, this code has spelling mistakes. "deferred" is with one "f", and so the call promise.deffer() will give a runtime error. It should be .defer(). Also rpotractor is misspelled. Your code could not even run.
Secondly, you are using an anitpattern: there is no need to create a promise/deferred, when you already get a promise object from browser.wait. Just return that one (or one returned from a then chain).
Also, return element is a wrong return value. It executes synchronously, so before the waiting is over, and the then chain will kick in too soon. Instead, make sure to return the promise that browser.wait returns.
You could do something like this:
var check_availibility = function(counter, totalCount, element){
if (counter <= totalCount) {
return browser.wait(function () {
return browser.wait(EC.visibilityOf(element), 2000)
// ^^^^^^
}).then(function () {
console.log('Element found.')
return true;
}).catch(function (err) {
return inprogressTab.click()
// ^^^^^^
.then(() => availableTab.click())
.then(() => check_availibility(counter+1 , totalCount, element))
})
} else {
return protractor.promise.rejected(false);
}
}
Note that JavaScript has native Promise support since EcmaScript2015, so instead of using protractor.promise, you could just use Promise.
I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?
I'm wanting to run a forEach loop then return (resolve?) a Promise so that my next function can run. I can correctly work out when the forEach is at the end but just dont know how to resolve the Promise.
Here is my code:
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if(i == submittedFields.length - 1) {
console.log('finished');
}
});
}
The reason I want to return a Promise is because I want to wait until this function has run then run another function like this:
this.submissionsProvider.addSubmissionsForms(submissionKey, this.submittedFields)
.then(() => {
this.navCtrl.pop();
});
So if you want to use promises you can do the following:
As a side note you can resovle any value e.g. resolve('finished')
addSubmissionsForms(submissionId: string, submittedFields:Array<any>): Promise<any> {
return new Promise((resolve, reject) => {
const submissionPath:firebase.database.Reference = this.rootRef.child(`submissionsByID/${submissionId}/fields`);
submittedFields.forEach(function(submittedField, i) {
let fieldId = submittedField.id;
submissionPath.child(fieldId).update(submittedField);
if (i == submittedFields.length - 1) {
resolve();
}
});
reject();
});
}
If the function addSubmissionsForms does nothing async (like e.g. an ajax call or reading and writing into database) you don't really need promises because you just can execute functions one after another
addSubmissionsForms();
nextFunction();
I need to iterate through an array and save every object to the Database.
At the end I need a callback with an array of all of the saved and failed object.
Below is the code I have:
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId')
db.List.create(element).then((list) => {
savedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
if (index == body.length - 1) {
callback(savedObjects, failedObjects)
}
})
})
}
The code above works. Is there a way better to accomplish this?
I would recommend the following approach using Promise.all() to run the db.List.create() in parallel as it will return a Promise. By mapping the body array elements to Promises you can achieve better performance as they will run in parallel (and not have to track the complete count).
exports.addList = (app, body, callback) => {
var savedObjects = [];
var failedObjects = [];
Promise.all(
// map the array to return Promises
body.map(element => {
const list = _.pick(element, 'userAId','userBId');
return db.List.create(list)
.then(() => savedObjects.push(list))
.catch((error) => {
if (error.name === 'SequelizeUniqueConstraintError') {
failedObjects.push(list)
}
})
})
)
// when all Promises have resolved return the callback
.then(() => callback(savedObjects, failedObjects));
}
In your example your complete callback will always fire after the first promise is complete. This is because the create function is asynchronous while the surrounding loop is not, therefore the loop will have already completed by the time your first callback is triggered.
In your scenario this means element and index will always be the last in the loop. One way around this would be to move your promise chain into it's own function.
In this example I've used an extra flag to track the number of completed promises to trigger your complete method.
exports.addList = (app, body, callback) => {
var savedObjects = []
var failedObjects = []
var complete = 0;
function createElement(element){
db.List.create(element).then((list) => {
savedObjects.push(element)
}).catch((error) => {
if (error.name === "SequelizeUniqueConstraintError") {
failedObjects.push(element)
}
}).finally(() => {
complete++;
if(complete == body.length) {
callback(savedObjects, failedObjects)
}
});
}
body.forEach((element, index) => {
body[index] = _.pick(element, 'userAId','userBId');
createElement(element);
})
}