I am writing a script in node.js for selenium that will go to a page and grab the innerhtml of a certain css class and store them in an array.
var element = driver.findElement(By.className("hll"));
element.getInnerHtml().then(html){
//returns 1 of the elements html where I want multiples
}
To retrieve the html of multiple elements, you can use driver.findElements() to find all matches elements. This will provider a Promise that resolves with the elements in an Array.
var pendingElements = driver.findElements(By.className('h11'))
pendingElements.then(function (elements) {
// ...
});
You'll need to iterate over the collection and request each element's HTML. You can use the Array's .map() to create a collection of promises from getInnerHtml():
var pendingHtml = elements.map(function (elem) {
return elem.getInnerHtml();
});
To wait for them to be resolved, you can pass the collection to promise.all().
promise.all(pendingHtml).then(function (allHtml) {
// ...
});
Note, you'll need a reference to Selenium's promise for that.
var promise = require('selenium-webdriver').promise;
Combined:
// ...
var promise = require('selenium-webdriver').promise;
var pendingElements = driver.findElements(By.className('h11'))
pendingElements.then(function (elements) {
var pendingHtml = elements.map(function (elem) {
return elem.getInnerHtml();
});
promise.all(pendingHtml).then(function (allHtml) {
// `allHtml` will be an `Array` of strings
});
});
Related
I am working with SharePoint making JSOM calls. I need to get results and not move on until ALL data is retrieved. I have tried so many examples (none are complete enough for me to understand how to tailor to my issue with nested $.each loop statements. I seem to get close, but never anything working correctly.
I have edited my actual code (minus the top 3 variables as they are passed in from another page) to incorporate Tomalak's work so it we can figure it out better. Currently, the results are coming up with empty objects. Trying to figure out what I;m doing wrong.
[EDITED 06 AUG 2018]
Finally got it to work. there was only two tiny problems I found with the provided code :-). I will try and bold them.
var fya = [2008,2009]; //Fiscal Year Array which we use to know what lists to look at
var assignedRecords = []; //Global Reusable Variable
var assignedCourses = ['Math','Science','Reading']; //There might not be records who are associated with a particular course in each list. Wee need to pull the student records (assignedRecords) assoiciated with the above in 2008 and 2009.
SP.ClientContext.prototype.executeQueryPromise = function (items) {
var result = $.Deferred();
this.load(items);
this.executeQueryAsync(function (sender, args) { result.resolve(items) },
function (sender, args) { result.reject(args) });
return result.promise();
};
Move 'var arr = [];' outside of arrayFromCollection
Add 'var' to the e variable in that same function
var arr = [];
function arrayFromCollection(coll) {
var e = coll.getEnumerator();
while (e.moveNext()) arr.push(e.get_current());
return arr;
};
function queryListPromise(title, course) {
var list = hostWeb.get_lists().getByTitle(title);
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where>'
+ '<Eq><FieldRef Name="Course"/><Value Type="Text">' + course + '</Value></Eq>'
+ '</Where></Query></View>');
return context.executeQueryPromise(list.getItems(camlQuery)).then(arrayFromCollection);
};
function GetAssignedApplications() {
assignedRecords = []; //Need to start empty
var myCourses = assignedCourses;
$.each(myCourses, function (i, myCoursesItem) {
var courseName = myCoursesItem.get_lookupValue();
Change '$.forEach' to '$.each'
$.each(fya, function (n, fyaItem) {
var listTitle = "FY" + String(fyaItem).substring(2); //FY18 & FY19 are the names of the actual lists.
assignedRecords.push(queryListPromise(listTitle, courseName));
});
});
$.when(assignedRecords).then(function (results) {
return Array.prototype.concat.apply([], results);
}).then(function (items) {
items.forEach(function (item) {
var a = item; //item is empty and this actually runs before arrayFromCollection and it returns duplicate records (4) when there is only 2.
});
}).fail(onError);
};
So first things first, waiting on multiple promises in jQuery is done with $.when().
$.when(p1, p2, p3).then(function (results) {
// results is an array of length 3
});
In order to make this work with a variable argument count, we use Function#apply:
var promises = [p1, p2, p3]; // could be be of any length now
$.when.apply($, promises).then(function () {
var results = [].slice.call(arguments);
// results is an array of the same length as promises
});
In order to make this reusable, let's call that $.whenAll:
$.whenAll = function (promises) {
return $.when.apply($, promises).then(function () {
return [].slice.call(arguments);
});
};
Now we can do:
$.whenAll([p1, p2, p3]).then(function (results) {
// results is an array of length 3
});
Next, we need to turn the callback-based executeQueryAsync() into a promise-based function. In general, promisification follows this scheme:
function somethingPromise(args) {
var result = $.Deferred();
somethingAsync(/* onSuccess */ result.resolve, /* onError */ result.reject);
return result.promise();
}
This expects that the onSuccess and onError handlers receive data as arguments. That's not the case with executeQueryAsync - it modifies the items directly. However, we still want to resolve the promise with items, so we need to do that explicitly.
Let's slap that onto the SP.ClientContext prototype right-away for re-use.
SP.ClientContext.prototype.executeQueryPromise = function (items) {
var result = $.Deferred();
this.load(items);
this.executeQueryAsync(
function (sender, args) { result.resolve(items); },
function (sender, args) { result.reject(args); }
);
return result.promise();
};
Next we should set up a helper that turns those unwieldy collections into something usable.
function arrayFromCollection(coll) {
var arr = [], e = coll.getEnumerator();
while (e.moveNext()) arr.push(e.get_current());
return arr;
}
With that all done, we can extract a function that takes a title and returns a promise for an item array.
function queryListPromise(title) {
var list = web.get_lists().getByTitle(title);
var q = new SP.CamlQuery();
//...
return ctx.executeQueryPromise(list.getItems(q)).then(arrayFromCollection);
}
And finally, since you want a flat list of items, we should start with a flat list of queries:
function GetNextFunction() {
var queries = [];
// build flat list of queries
$.each(arr1, function(i, arr1Item) {
$.each(arr2, function(n, arr2Item) {
queries.push(queryListPromise(arr2Item));
});
});
// wait on all queries
$.whenAll(queries).then(function (results) {
// flatten incoming array of arrays
return Array.prototype.concat.apply([], results);
}).then(function (items) {
// items contains all items from all queries
items.forEach(function (item) { /* ... */ });
}).fail(onError);
}
Here queries becomes a flat array of promises, each promise will resolve to an array of items. Therefore, results will be an array of arrays of items.
I have this below code, but as soon as widgetsAddCall is added into array, it gets executed, and promise.each is of no use.
Function widgetsAddCall is making async request to API server and returns bluebird promisified request. I want that API call is made one after another, so that each call sends chunk of data one after another.
var chunkCnt = Math.ceil(widgetsIds.length/4000);
var responseT = Array();
var Promise = require('bluebird');
for(var cntTemp =0 ; cntTemp<chunkCnt;cntTemp++){
var tempWidgs = widgetsIds.slice(cntTemp,cntTemp+4000);
var query = {
id: tempWidgs.join(',')
};
responseT.push(widgetsAddCall(tempWidgs,query,campRemoteId,campaign));
}
return Promise.each(responseT,function(responses) {
// Use the responses here
return getWidgets(campRemoteId,campaign).then((ids) => {
var toRemove = [];
for(var id of ids){
if(widgetsIds.indexOf(id)===-1){
toRemove.push(id);
}
}
if(toRemove.length) {
return removeWidgets(campaign, campRemoteId, toRemove);
}
});
})
I want that API call is made one after another, so that each call
sends chunk of data one after another.
The simplest way I can think of to serialize all your calls is to prebuild your chunks into an array and then use Bluebird's Promise.mapSeries() to serially iterate through the array:
var Promise = require('bluebird');
// pre-build chunks into an array
var chunks = [];
var chunkCnt = Math.ceil(widgetsIds.length / 4000);
for (var chunkIndex = 0; chunkIndex < chunkCnt; chunkIndex++) {
chunks.push(widgetsIds.slice(cntIndex,cntIndex + 4000));
}
// now serially iterate the array
Promise.mapSeries(chunks, function(item) {
return widgetsAddCall(item, {id: item.join(',')}, campRemoteId, campaign);
}).then(function(results) {
// now process the results of widgetsAddCall() here
});
FYI, your original use of Promise.each() was not making any sense to me because you were iterating an array of promises, but then not using any info from the iteration. There seemed to be no point to the iteration. Plus, you weren't properly serializing your calls to widgetsAddCall() anyway since you were launching all those calls in parallel.
I'm kinda new to this q stuff and I find it pretty awesome, but there's something I still can't figure out.
I managed to run some combined promises with q.all by passing q.all an array of promises. Something like this..
var promises = [promiseOne(), promiseTwo()];
q.all(promises).then(function (results) {
res.send(results);
} );
The thing with this is that I would actually want those promises to be named, so I don't have to rely in the order of the promises.
I read somewhere that you can actually pass an object to q.all, to have the results named. So that would be something like this:
var promises = { promiseOne: promiseOne(), promiseTwo: promiseTwo() }
q.all(promises).then(function(results){
res.send(results);
});
But I guess this just doesn't work the same way as sending an array as I'm not getting the results of my promises in there. The result I get is similar to this one:
{
promiseOne: {
source: {}
},
promiseTwo: {
source: {}
}
}
So how would you go about getting named results from q.all?
One thing to note is that the amount of promises I will have in the promises array is not fixed as I get that from a GET param sent by the user to my function.
Also, inside each of my promises I have another array (or object) of promises to be resolved and whose results I would like to be named as well.
Here is another way to write the code Roamer wrote with the functionality you asked for (return an object):
Q.props = obj => {
const ps = Q.all(Object.keys(obj).map(x => Q.all([x, obj[x]])));
return ps.then(x => x.reduce((p, c) => {p[c[0]] = c[1]; return p } , {}));
};
Which would let you do:
Q.props(promises).then(o => {
o.promiseOne
});
Although you should consider using bluebird if you want all these helper functions.
Q.js appears not to offer Q.all(object), therefore you will need to map your object to an array before passing to Q.all()
Something like this will be reusable and convenient :
Q.allObj = function (obj) {
var array = Object.keys(obj).map(function(key, i) {
try {
//expect a promise
return obj[key].then(function(value) {
return {key: key, value: value};
});
}
catch(e) {
// whoops it was a value
return {key: key, value: obj[key]};
}
});
return Q.all(array);
};
Use as follows :
Q.allObj(myObj).then(function(results) {
results.forEach(function(obj) {
var name = obj.key;
var value = obj.value;
...
});
});
Dears ,
How can i run promises in nodejs sequentially , in the following example am looping through array of hours then for each fetched hour get result from the database , the issue here : am getting results but i want it sequentially same order that i got hours .
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
Thanks ,
var promise = Promise.resolve(); // make an empty promise in the way you do it with your promise library
angular.forEach(SharedVar.getCategories(), function (h) {
promise.then(function() {
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {});
});
});
The way to do it sequently would be to do one Request and do the next request inside the promise.
I think the better approach by far is to extend your SharedVar.setDataS(d) function in a way, that it does not depend on getting the data sequentially. Like having a SharedVar.setDataS(d, index) and using the config var in your $http.get (or whatever) functioncall inside your RESTApi to promote that index all the way to the promise.
If your RESTApi looks like this:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
return $http.get(url).data;
}
// Some other Methods...
Then you need a way to pass something to "reorder" your Data when it arrives asynchronously, this could be a index you count up or in your case maybe the hour Variable:
var RESTApi = {
getAnswerdCallsByHour : function(hour) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.hour = hour;
return $http.get(url, config); // Return the promise not just data or a specific field
}
// Some other Methods...
Now when your promise is fullfiled you can access your "hour" Variable like so:
var d = SharedVar.getDataS();
d[promise.config.hour] = promise.data;
SharedVar.setDataS(d);
Now you know what piece of data correlates to which request and you do not need to recieve Data in order. The last piece only works properly when hours runs sequential from 0 to 23, if that isn't the case you need to:
var RESTApi = {
getAnswerdCallsByHour : function(hour, index) {
var url = "bla.com/myservice?hour=" + hour;
var config = [];
config.index = index;
return $http.get(url, config);
}
// Some other Methods...
...
...
var d = SharedVar.getDataS();
d[promise.config.index] = promise.data;
SharedVar.setDataS(d);
Safari's answer is how I typically handle this. (Sorry, I don't have enough rep to comment yet...) You were experiencing problems with it because the example provided does not capture and use the new promise in subsequent loops. See my comments on the slightly modified version here:
var promise = Promise.resolve();
angular.forEach(SharedVar.getCategories(), function (h) {
t = h.split('-', 2);
t = t[0];
// You must capture the new promise here; the next loop will wait
// for the promise returned from getAnswerdCallsByHour to resolve.
promise = promise.then(function() {
// Halt downstream promises until this returned promises finishes
return RESTApi.getAnswerdCallsByHour(t).then(function (answerdTotal) {
$scope.answerdCallsTotalByHour.push(answerdTotal);
var d = SharedVar.getDataS();
d[count] = answerdTotal;
SharedVar.setDataS(d);
count++;
});
});
});
I need to use bluebird in my code and I have no idea how to use it. My code contains nested loops. When the user logs in, my code will run. It will begin to look for any files under the user, and if there are files then, it will loop through to get the name of the files, since the name is stored in a dictionary. Once it got the name, it will store the name in an array. Once all the names are stored, it will be passed along in res.render().
Here is my code:
router.post('/login', function(req, res){
var username = req.body.username;
var password = req.body.password;
Parse.User.logIn(username, password, {
success: function(user){
var Files = Parse.Object.extend("File");
var object = [];
var query = new Parse.Query(Files);
query.equalTo("user", Parse.User.current());
var temp;
query.find({
success:function(results){
for(var i=0; i< results.length; i++){
var file = results[i].toJSON();
for(var k in file){
if (k ==="javaFile"){
for(var t in file[k]){
if (t === "name"){
temp = file[k][t];
var getname = temp.split("-").pop();
object[i] = getname;
}
}
}
}
}
}
});
console.log(object);
res.render('filename', {title: 'File Name', FIles: object});
console.log(object);
},
error: function(user, error) {
console.log("Invalid username/password");
res.render('logins');
}
})
});
EDIT:The code doesn't work, because on the first and second console.log(object), I get an empty array. I am suppose to get one item in that array, because I have one file saved
JavaScript code is all parsed from top to bottom, but it doesn't necessarily execute in that order with asynchronous code. The problem is that you have the log statements inside of the success callback of your login function, but it's NOT inside of the query's success callback.
You have a few options:
Move the console.log statements inside of the inner success callback so that while they may be parsed at load time, they do not execute until both callbacks have been invoked.
Promisify functions that traditionally rely on and invoke callback functions, and hang then handlers off of the returned value to chain the promises together.
The first option is not using promises at all, but relying solely on callbacks. To flatten your code you will want to promisify the functions and then chain them.
I'm not familiar with the syntax you're using there with the success and error callbacks, nor am I familiar with Parse. Typically you would do something like:
query.find(someArgsHere, function(success, err) {
});
But then you would have to nest another callback inside of that, and another callback inside of that. To "flatten" the pyramid, we make the function return a promise instead, and then we can chain the promises. Assuming that Parse.User.logIn is a callback-style function (as is Parse.Query.find), you might do something like:
var Promise = require('bluebird');
var login = Promise.promisify(Parse.User.logIn);
var find = Promise.promisify(Parse.Query.find);
var outerOutput = [];
return login(yourArgsHere)
.then(function(user) {
return find(user.someValue);
})
.then(function(results) {
var innerOutput = [];
// do something with innerOutput or outerOutput and render it
});
This should look familiar to synchronous code that you might be used to, except instead of saving the returned value into a variable and then passing that variable to your next function call, you use "then" handlers to chain the promises together. You could either create the entire output variable inside of the second then handler, or you can declare the variable output prior to even starting this promise chain, and then it will be in scope for all of those functions. I have shown you both options above, but obviously you don't need to define both of those variables and assign them values. Just pick the option that suits your needs.
You can also use Bluebird's promisifyAll() function to wrap an entire library with equivalent promise-returning functions. They will all have the same name of the functions in the library suffixed with Async. So assuming the Parse library contains callback-style functions named someFunctionName() and someOtherFunc() you could do this:
var Parse = Promise.promisifyAll(require("Parse"));
var promiseyFunction = function() {
return Parse.someFunctionNameAsync()
.then(function(result) {
return Parse.someOtherFuncAsync(result.someProperty);
})
.then(function(otherFuncResult) {
var something;
// do stuff to assign a value to something
return something;
});
}
I have a few pointers. ... Btw tho, are you trying to use Parse's Promises?
You can get rid of those inner nested loops and a few other changes:
Use some syntax like this to be more elegant:
/// You could use a map function like this to get the files into an array of just thier names
var fileNames = matchedFiles.map(function _getJavaFile(item) {
return item && item.javaFile && item.javaFile.name // NOT NULL
&& item.javaFile.name.split('-')[0]; // RETURN first part of name
});
// Example to filter/retrieve only valid file objs (with dashes in name)
var matchedFiles = results.filter(function _hasJavaFile(item) {
return item && item.javaFile && item.javaFile.name // NOT NULL
&& item.javaFile.name.indexOf('-') > -1; // and has a dash
});
And here is an example on using Parse's native promises (add code above to line 4/5 below, note the 'then()' function, that's effectively now your 'callback' handler):
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.select("score", "playerName");
query.find().then(function(results) {
// each of results will only have the selected fields available.
});