Scenario 1) Stale Element Reference error
it("should have all elements", function (done) {
var def = protractor.promise.defer();
var prm = browser.findElements(by.css("jive")).then((elements) => {
elements.forEach((ele) => {
var id = ele.getId();
var loc = ele.getLocation();
var inner = ele.getInnerHtml();
var text = ele.getText();
var tag = ele.getTagName();
});
def.then((wtf) => {
debugger;
});
});
done();
});
I thought that the code above would have a ton of promises on the queue after running through the iteration on all elements. But when the def.then statement runs Selenium is telling me I have Stale Elements. The iteration is running through the elements for that css.
I was hoping to get an array of resolved promises for everything the iteration requested...
Scenario 2) Gives Stale Element Reference
var promises = Array<webdriver.promise.Promise<any>>();
var allElements: webdriver.IWebElement[] = [];
it("should have all elements", function (done) {
var alllinks: webdriver.WebElement[];
browser.controlFlow().execute(() => {
browser.findElements(by.tagName("a")).then((works) => {
alllinks = works;
alllinks.forEach((ele) => {
promises.push(ele.getAttribute("href"));
promises.push(ele.getId());
promises.push(ele.getInnerHtml());
promises.push(ele.getLocation());
});
//getting stale reference here...
protractor.promise.all(promises).then((wtf) => {
debugger;
});
debugger;
});
});
Please advise.
I think you need the map():
var links = element.all(by.tagName("a")).map(function (link) {
return {
href: link.getAttribute("href"),
id: link.getId(),
innerHTML: link.getInnerHtml(),
location: link.getLocation()
}
});
Now the links would contain a promise resolving into an array of objects.
My tiny example might be helpful for you:
viewBookingDetailsPage.roomtypeAttributeForParticularDay = function (day, roomTypeNumber, selector) {
var deferred = protractor.promise.defer();
element.all(by.repeater(viewBookingDetailsPage.guestroomInfo.allDays)).then(function (days) {
days[day].all(by.repeater(viewBookingDetailsPage.guestroomInfo.roomTypesInDay)).then(function (roomTypes) {
deferred.fulfill(roomTypes[roomTypeNumber].$(selector));
});
});
return deferred.promise;
};
You need to use fulFill to return result after promise will be successfully resolved.
Related
In the code below, I am trying to do the following:
Have Stats(), getOverallStats() and GetGroups() to run in parallel. Each returns a promise.
The forEach in GetGroups.then() should run sequentially to ensure the output is in the correct order.
Once ALL of the above is complete, then run some more code.
However, I am getting very confused with the promises! The logging gives me:
looping
here
looping
looping
But what I am looking for is here to be at the end.
Finally, at the moment I have hardcoded loopAgelist[1] for testing purposes. But, I actually want to be able to loop through loopAgelist[] with a timeout in between! I would appreciate if someone could explain some promise 'rules' to use in these complicated cases.
var loopAgeList;
var looppromises = [];
getAgeGroupList().then(function (loopAgeList) {
var statsPromise = Stats(loopAgeList[1]);
var oStatsPromise = getOverallStats();
var grpPromise = GetGroups(loopAgeList[1]).then(function (groups) {
var promise = Parse.Promise.as();
groups.forEach(function (grp) {
promise = promise.then(function () { // Need this so that the tables are drawn in the correct order (force to be in series)
console.log("looping")
if (grp != "KO"){
var standingsPromise = Standings(loopAgeList[1], grp);
looppromises.push(standingsPromise);
}
var fixPromise = GetFixtures(loopAgeList[1], grp);
looppromises.push(fixPromise);
return fixPromise;
});
});
return Parse.Promise.all(looppromises);
});
var promises = [statsPromise, oStatsPromise, grpPromise, looppromises];
Parse.Promise.all(promises).then(function(results) {
console.log("here");
});
});
The rewrite can be improved significantly by adopting a couple simple style rules: (1) there's no need to create a resolved promise and then chain to it (in fact, most would consider this an anti-pattern), (2) promises to be run together by iterating an array of operands is the perfect application of array .map (not reduce), (3) most importantly, smaller, testable, promise-returning functions always clears up the mystery.
Putting all that together, the main function can be as simple as this...
function loopOverOnce(agegroup) {
let statsPromise = Stats(agegroup);
let oStatsPromise = getOverallStats();
let grpPromise = GetGroups(agegroup).then(function(groups) {
return getStandingsAndFixturesForGroups(groups, agegroup);
});
return Parse.Promise.all([statsPromise, oStatsPromise, grpPromise]);
}
Let's write getStandingsAndFixturesForGroups. It's only job will be map the groups and aggregate promises to do work on each...
function getStandingsAndFixturesForGroups(groups, agegroup) {
let promises = groups.map(function(group) {
return getStandingsAndFixturesForGroup(group, agegroup);
});
return Parse.Promise.all(promises);
}
Now, getStandingsAndFixturesForGroup, a function to do the async work on a single group, conditionally for part of the work...
function getStandingsAndFixturesForGroup(group, agegroup) {
let promises = (group != "KO")? [ Standings(agegroup, grp) ] : [];
promises.push(GetFixtures(agegroup, group));
return Parse.Promise.all(promises); // this is your standings promise (conditionally) and fixtures promise
}
Done. I'd test this code in the reverse order that it's presented here.
EDIT The OP also asks how to perform several promises, serially, interspersed with timeouts. Here's my advice.
First, a slightly simpler version of your delay function, which is a good example when it is right to create a new promise (because there's nothing at bottom to call to get one)
function delay(interval) {
return new Promise(function(resolve, reject){
setTimeout(function() {resolve();}, interval);
});
};
And reducing is a good way to build a list of promises, including interspersed delays...
getAgeGroupList().then(function (loopAgeList) {
loopAgeList.reduce(function(promise, agegroup) {
return promise.then(function() {
let promises = [loopOverOnce(agegroup), delay(15000)];
return Promise.all(promises);
});
}, Promise.as());
});
A couple notes: this results in a sequence like loopOverOnce, timeout, loopOverOnce, timeout, ... etc.. If you'd like a timeout first, reverse the order of the little chain in the inner loop:
[ delay(15000), loopOverOnce(agegroup) ]
Final note is that all this could be made even shorter and prettier by adopting ES6 fat arrow syntax for anonymous functions, e.g.
loopAgeList.reduce((promise, agegroup) => {
promise.then(() => Promise.all([loopOverOnce(agegroup), delay(15000)]));
}, Promise.as());
I have re-written it using reduce and seem to have it working. Comments on this would be welcome (i.e. are there any issues with this code).
function loopOverOnce(agegroup) {
var statsPromise = Stats(agegroup);
var oStatsPromise = getOverallStats();
var grpPromise = GetGroups(agegroup).then(function (groups) {
function getStandingsAndFixtures(groups) {
var promise = Parse.Promise.as();
return groups.reduce(function (promise, grp) {
return promise.then(function (result) {
var standingsPromise = Parse.Promise.as();
if (grp != "KO") {
standingsPromise = Standings(agegroup, grp);
}
var fixPromise = GetFixtures(agegroup, grp);
console.log("fixPromise");
return Parse.Promise.all([standingsPromise, fixPromise]);
});
}, promise);
}
var sfPromise = getStandingsAndFixtures(groups).then(function () { console.log("Test1") });
return sfPromise;
});
return Parse.Promise.all([statsPromise, oStatsPromise, grpPromise]).then(function () { console.log("Test2") });
}
getAgeGroupList().then(function (loopAgeList) {
// https://stackoverflow.com/questions/39538473/using-settimeout-on-promise-chain
function delay(t, v) {
return new Promise(function (resolve) {
setTimeout(resolve.bind(null, v), t)
});
}
var promise = Parse.Promise.as();
loopAgeList.reduce(function (promise, agegroup) {
return promise.then(function () {
return delay(15000).then(function () {
return loopOverOnce(agegroup);
});
});
}, promise);
});
The problem is, that you pass a nested array to Promise.all:
var promises = [statsPromise, oStatsPromise, grpPromise, looppromises];
Just flatten that:
var promises = [statsPromise, oStatsPromise, grpPromise, ...looppromises];
// Or
var promises = [statsPromise, oStatsPromise, grpPromise].concat(looppromises);
However you still need to await promise somewhere to ensure that the chain finished its execution, otherwise looppromise will be empty.
All in all it might be better to use async / await to make everything much more readable:
(async function() {
const ageGroups = await getAgeGroupList();
const statsPromise = Stats(ageGroups[1]);
const overallStatsPromise = getOverallStats();
const groups = await GetGroups(ageGroups[1]);
for(const group of groups) {
const [standings, fixtures] = await Promise.all(
Standings(ageGroups[1], group),
GetFixtures(ageGroups[1], group)
);
// Do something with standings & fixtures
}
const [stats, overallStats] = await Promise.all(statsPromise, overallStatsPromise);
// Do whatever you want with stats etc.
})();
Currently I have following code:
var detailPromises = links.map(function (link) { return requestP(link) });
var oldPollNames = [];
// add a database call to the promises as it also can resolved during detail fetching
detailPromises.push(db.polls.findAsync({}, {title: 1}));
return Promise.all(detailPromises)
.then(function (data) {
oldPollNames = data.pop().map(function (item) { return item.title; });
return data;
})
.then(function (htmls) {
var newPolls = [];
htmls.forEach(function (i, html) {
// some processing of html using oldPollNames (newPools.push...)
});
return newPolls;
})
.then(/* insert into db */);
What I'm doing is: waiting for db + waiting for html requests and then process booth.
I'm wondering if it would make more sense / be more performant to do following:
var detailPromises = links.map(function (link) { return requestP(link) });
return db.polls.findAsync({}, {title: 1}).then(function(polls) {
var oldPollNames = polls.map(function (item) { return item.title; });
var newPolls = [];
detailPromises = detailPromises.map(function (p) {
return p.then(function (html) {
// some processing of html using oldPollNames (newPools.push...)
})
});
return Promise.all(detailPromises)
.done(function () {
// insert newPolls into db
});
});
The advantage of the second approach imo is that each request gets (already) handled when it is done and can do it's procesing before all / other promises are fulfilled.
I'm wondering if it would make more sense / be more performant
Yes, it would be more performant to process each html request separately and as fast as possible, instead of waiting for all of them and then processing them together in a huge loop. Not only will they processed earlier, you also avoid a possibly long-running loop of heavy processing.
However, the approach also has its drawbacks: it's more complicated to implement. The code you've given is prone to reporting Unhandled Rejections if any of the detailPromises rejects before the database query fulfills, or if any of them rejects and the database query is rejected as well. To prevent that, you'll need to use Promise.all on all the promises anyway:
var detailPromises = links.map(requestP);
var resultPromise = db.polls.findAsync({}, {title: 1}).then(function(polls) {
var oldPollNames = polls.map(function(item) { return item.title; });
var newPollPromises = detailPromises.map(function(p, i) {
return p.then(function(html) {
// some processing of html
});
});
return Promise.all(newPollPromies) // not the detailPromises!
.then(function(newPolls) {
// insert newPolls into db
});
});
return Promise.all([resultPromise].concat(detailPromises)).then(function(r) {
return r[0];
});
In addition to Bergi's answer I think I found another likewise solution:
var detailPromises = links.map(requestP);
var pollNamePromise = db.polls.findAsync({}, {title: 1}).then(function(polls) {
return polls.map(function (item) { return item.title; });
});
var newPromises = detailPromises.map(function(p) {
return Promise.join(p, pollNamePromise, function(html, oldPollNames) {
// some processing of html using oldPollNames (newPools.push...)
});
});
Promise.all(newPromises).then(function(newPolls) {
// insert newPolls into db
});
Edit Changed from Promise.all to Promise.join which is available in Bluebird
So i have this function where i need to combine multiple promises responses, but after some reading i realized promises are async so in this case my loop is going to complete before all the responses do. Should i need to use something like $q.all in this case? How can i improve this piece of code? Thanks..
$scope.messages = [];
function getPastMessages(data) {
angular.forEach(data, function(item) {
Message.get(item.id).then(function(msg) {
if (msg.data.is_private === false) {
User.getPictures(msg.data.user.id).then(function(pics) {
msg.data.user.pictures = pics.data;
});
} else {
User.get(msg.data.im.sender).then(function(sender) {
msg.data.im.sender = sender.data;
User.get(msg.data.im.reciever).then(function(reciever) {
msg.data.im.reciever = reciever.data;
});
});
}
console.log(msg.data); // SHOW 4 OBJECTS CORRECT
$scope.messages.push(msg.data);
console.log($scope.messages); // SHOW ARRAY OF 6 OBJECTS ????????
})
});
};
Without a working example it was difficult to fully understand the context of your code, but you can do something similar to this.
The basic idea is to create a list of promises that you need to wait for. Each of the promises within this list should return a result (presumably msg.data). Using $q.all, you'll get a list of results (one from each promise) at the end. Note that things returned within a .then get wrapped in promises if they aren't already promises.
$scope.messages = [];
function getPastMessages(data) {
var promises = [];
angular.forEach(data, function(item) {
promises.push(getMessage(item));
});
return $q.all(promises);
}
function getMessage(item) {
return Message.get(item.id).then(function(msg) {
if (msg.data.is_private === false) {
return User.getPictures(msg.data.user.id).then(function(pics) {
msg.data.user.pictures = pics.data;
return msg.data;
});
} else {
return User.get(msg.data.im.sender).then(function(sender) {
msg.data.im.sender = sender.data;
return User.get(msg.data.im.reciever).then(function(reciever) {
msg.data.im.reciever = reciever.data;
return msg.data;
});
});
}
});
}
Usage:
getPastMessages(data).then(function(results) {
for (var i = 0; i < results.length; i++) {
$scope.messages.push(results[i]);
}
});
You can only rely on promises to have resolved when you're inside their callback functions.
var messages = [];
somethingAsync().then(function(data){
messages.push(data);
});
console.log(messages.length)
might return 0 or 1 depending on how long somethingAsync takes; you can't rely on it having completed.
Instead you should do your debugging from inside the callback function:
var messages = [];
somethingAsync().then(function(data){
messages.push(data);
console.log(messages.length)
});
This will always return 1.
I'm using Parse Cloud functions for mobile apps, but all of them follow asynchronous nature. Hence to overcome that nature, I started using javascript Promises.
But promises also not giving me the desired output.
The problem is : second-then is executed after the third-then. And in third-then the parameter of getRecommendedGroup groups is getting []
getRecommendedGroup(request.params.email, groups, function(data){
this is a callback
});
Basically groups is an output of 2nd then.
So how should I write my code inorder to make 2nd-then execute before 3rd-then.
Below is the code snippet
Parse.Cloud.define("getGroup", function(request, response) {
var grpMember = new Parse.Query("GroupMember"),
groupIds = [],
groupObj = {};
grpMember.equalTo("user", request.params.email);
//from the groupMember we're taking groupId
grpMember.find()
.then(function(grpMemberResponse) {
grpMemberResponse.forEach(function(value, index) {
var memberObj = value;
groupIds.push(memberObj.get("groupId").id);
});
return groupIds;
})
//with this groupId we're retriving group and pushing it to an groupArr
.then(function(groupIds) {
var groupArr = [];
var promises = [];
groupIds.forEach(function(value, index) {
alert("GroupId :: " + value);
promises.push(findGroup(value, function(group) {
groupArr.push(group);
});
});
return Parse.Promise.when(promises);
})
.then(function(groups) {
alert("GroupArr :: " + JSON.stringify(groups));
getRecommendedGroup(request.params.email, groups, function(data) {
groupObj["own"] = groups;
groupObj["recommended"] = data;
response.success(groupObj);
});
});
});
var findGroup = function(objectId, callback) {
var groupQuery = new Parse.Query("Group");
groupQuery.get(objectId, {
success: function(group) {
alert("Group Obj Id ::" + group.id);
callback(group);
},
error: function(error) {
alert("Error while finding Group " + error);
}
});
};
The second then is running second, but it is calling an asynch function. In order to sequence properly, those promises being launched by the loop must be fulfilled. Fortunately, Promise.when() does that...
.then(function(groupIds) {
var findPromises = []
groupIds.forEach(function(value, index) {
findPromises.push(findGroup(value, function(group));
)};
return Parse.Promise.when(findPromises);
}).then() {
// arguments is now a (var arg) array of results from running
// all of the findPromises promises
});
Edit - In order for this to work, the code needs to push a promise to do the find. That means findGroup must return a promise...
function findGroup(objectId) {
var groupQuery = new Parse.Query("Group");
return groupQuery.get(objectId);
}
I have this class:
(function(){
"use strict";
var FileRead = function() {
this.init();
};
p.read = function(file) {
var fileReader = new FileReader();
var deferred = $.Deferred();
fileReader.onload = function(event) {
deferred.resolve(event.target.result);
};
fileReader.onerror = function() {
deferred.reject(this);
};
fileReader.readAsDataURL(file);
return deferred.promise();
};
lx.FileRead = FileRead;
}(window));
The class is called in a loop:
var self = this;
$.each(files, function(index, file){
self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
});
My question is, is there a way to call a method once the loop has completed and self.fileRead has returned it's deferred for everything in the loop?
I want it to call the method even if one or more of the deferred fails.
$.when lets you wrap up multiple promises into one. Other promise libraries have something similar. Build up an array of promises returned by fileRead.read and then pass that array to $.when and hook up then/done/fail/always methods to the promise returned by .when
// use map instead of each and put that inside a $.when call
$.when.apply(null, $.map(files, function(index, file){
// return the resulting promise
return self.fileRead.read(file).done(function(fileB64){self.fileShow(file, fileB64, fileTemplate);});
}).done(function() {
//now everything is done
})
var self = this;
var processFiles = function (data) {
var promises = [];
$.each(files, function (index, file) {
var def = data.fileRead.read(file);
promises.push(def);
});
return $.when.apply(undefined, promises).promise();
}
self.processFiles(self).done(function(results){
//do stuff
});
$.when says "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters. In this case, you have an array of promises;
I know this is closed but as the doc states for $.when: In the multiple-Deferreds case where one of the Deferreds is rejected, jQuery.when immediately fires the failCallbacks for its master Deferred. (emphasis on immediately is mine)
If you want to complete all Deferreds even when one fails, I believe you need to come up with your own plugin along those lines below. The $.whenComplete function expects an array of functions that return a JQueryPromise.
var whenComplete = function (promiseFns) {
var me = this;
return $.Deferred(function (dfd) {
if (promiseFns.length === 0) {
dfd.resolve([]);
} else {
var numPromises = promiseFns.length;
var failed = false;
var args;
var resolves = [];
promiseFns.forEach(function (promiseFn) {
try {
promiseFn().fail(function () {
failed = true;
args = arguments;
}).done(function () {
resolves.push(arguments);
}).always(function () {
if (--numPromises === 0) {
if (failed) {
//Reject with the last error
dfd.reject.apply(me, args);
} else {
dfd.resolve(resolves);
}
}
});
} catch (e) {
var msg = 'Unexpected error processing promise. ' + e.message;
console.error('APP> ' + msg, promiseFn);
dfd.reject.call(me, msg, promiseFn);
}
});
}
}).promise();
};
To address the requirement, "to call the method even if one or more of the deferred fails" you ideally want an .allSettled() method but jQuery doesn't have that particular grain of sugar, so you have to do a DIY job :
You could find/write a $.allSettled() utility or achieve the same effect with a combination of .when() and .then() as follows :
var self = this;
$.when.apply(null, $.map(files, function(index, file) {
return self.fileRead.read(file).then(function(fileB64) {
self.fileShow(file, fileB64, fileTemplate);
return fileB64;//or similar
}, function() {
return $.when();//or similar
});
})).done(myMethod);
If it existed, $.allSettled() would do something similar internally.
Next, "in myMethod, how to distinguish the good responses from the errors?", but that's another question :)