I have a canvas pie chart that I'd like to test using protractor.
the html element looks like:
<canvas id ="my_pie" data-values="ctl.gfx.values" data-categories="ctl.gfx.categories"...
In my protractor code I am able to evaluate each attribute data-values and data-categories as follow:
...
var canvas = element(by.css("canvas#my_pie"));
...
canvas.evaluate("ctl.gfx.categories").then(function(c){
console.log(c);
});
canvas.evaluate("ctl.gfx.values").then(function(l){
console.log(v);
});
everything works fine and data is logged out on the console, but if I try to return the arrays themselves, they get returned as empty arrays, I understand this has something to do with promises as that's what the protractor evaluate function stands for but I cannot figure this out because I am totally new to JS, Jasmine and Protractor.
basically the expected logic should be like:
function getMyData(){
var myCategories = [];
var myValues = [];
canvas.evaluate("ctl.gfx.categories").then(function(c){
myCategories = c;
});
canvas.evaluate("ctl.gfx.values").then(function(v){
myValues = v;
});
// do something with both arrays, concat and return a result
//final statement would be a return statement.
}
what I would like to do is return the result of those arrays after some processing but to me it seems like the return statement runs always first, hence the empty result.
You not understand the Async programming/Promise in Protractor, please learn and make you clear the key points this link what to express: http://www.protractortest.org/#/control-flow#promises-and-the-control-flow
Then look into this link I answer other question to explain what happen when run Protracotr script: promise chaining vs promise.all
Then look below, I explained why you always get a empty array in code comment
function getMyData(){
var myCategories = [];
var myValues = [];
canvas.evaluate("ctl.gfx.categories").then(function(c){
myCategories = c;
});
canvas.evaluate("ctl.gfx.values").then(function(v){
myValues = v;
});
// Assume we concat the two array and return the new array as below code shows
var ret = myCategories.concat(myValues);
return ret;
// When JavaScript engine explain and execute below code line:
// var ret = myCategories.concat(myValues);
// the above two canvas.evaluate() have not resoved, because they are
// executed async not sync.
// so the myCategories and myValues have not assigned new value, they are
// still empty array at this time point, so always return empty array
}
To resolve your problem, try below code example:
function getMyData(){
return Promise.all([
canvas.evaluate("ctl.gfx.categories"),
canvas.evaluate("ctl.gfx.values")
]).then(function(data){
let myCategories = data[0];
let myValues = data[1];
return myCategories.concat(myValues);
});
}
getMyData().then(function(myData){
console.log(myData);
// put your code to consume myData at here
})
Related
I think it's a common question, but for concreteness the situation is:
I use the mammoth module to convert docx files to html. The module returns a promise.
I have an array of files and when I use a loop to create a promise for every file I need to know what promise returns me a result (to know what file was processed).
for(var i=0;i<filesPaths.length;i++){
mammoth.convertToHtml( {path: filesPaths[i]} )
.then(function(result){
filesHtml.push(result.value);
//here I need to know the value of filesPaths[i]
})
}
While writing the question, the answer became obvious (as is often the case :) ).
You can wrap the promise with a self invoked function and store any related info in local variable.
for(var i=0;i<filesPaths.length;i++){
(function(){
var fileName = filesPaths[i]; //or any other information related to promise
mammoth.convertToHtml( {path: filesPaths[i]} )
.then(function(result){
filesHtml.push({
text:result.value,
fileName:fileName
});
})
})()
}
You can use .map() array method (which is much like your solution in terms of function calls, but a bit cleaner):
filesPaths.map(function(fileName, i){
mammoth.convertToHtml({path: fileName})
.then(/* ... */)
;
});
// Here filesHtml is empty and you don't know when will be filled!!
...which is dirty (see the comment at the end).
Or you can simply use Promise.all() to collect the results:
var P = Promise.all(
filesPaths.map(function(fileName){
return mammoth.convertToHtml({path: fileName});
})
).then(function(resultArr){
return Promise.all(resultArr.map(function(result, i){
return {
text: text.value,
fileName: filesPaths[i],
};
}));
}).then(function(filesHtml){
/* Here you know filesHtml is fully filled */
});
P.then(function(filesHtml){
/* ...and here too */
});
This way, you also aren't messing things with global (or higher scope) variables.
To respond to your own answer with an alternative:
Creating functions in loops is not a great idea, it's a pretty good way to an unknown number of functions created. If you used a forEach loop you would get the same encapsulation in its callback function.
var arr = ['a', 'b', 'c'];
function prom(thing) {
return Promise.resolve(thing);
}
for (var i = 0; i < arr.length; i++) {
prom(arr[i]).then(function(val){
console.log(`for: got val ${val} with arr[${i}]`);
});
}
// Logs:
// "for: got val a with arr[3]"
// "for: got val b with arr[3]"
// "for: got val c with arr[3]"
arr.forEach(function(val, index) {
prom(val).then(function(val){
console.log(`forEach: got val ${val} with arr[${index}]`);
});
});
// Logs:
// "forEach: got val a with arr[0]"
// "forEach: got val b with arr[1]"
// "forEach: got val c with arr[2]"
I'm asking this question because I might have a fundamental misunderstanding of how bluebird's Promise.all works:
I'm having trouble understanding how I return the value from nested, dependent, Promise.alls.
I can get the desired result to display in console though. (you can see the data that I'm trying to return in the commented console.log).
For context, I'm writing a Hexo plugin that gets a collection of related blog posts then returns five of them.
Each promise depends on the data returned from the previous promise.
var Promise = require('bluebird')
var _ = require('underscore')
hexo.extend.helper.register("related_posts", function (site) {
var site = site
var post = this.page
var tags = post.tags
var title = post.title
var tagList = tags.map(function(tag){
return tag.name
})
// get 5 posts from each group and add them to a posts array
var PostsArray = []
Promise.all(tagList).then(function(items){
items.forEach(function(theTag){
PostsArray.push(site.tags.findOne({name: theTag}).posts.sort('date', -1).limit(25).toArray())
Promise.all(PostsArray).then(function(posts){
var thePosts = _.flatten(posts)
var finalListOfPosts = []
thePosts.forEach(function(post){
if(post.title != title){
finalListOfPosts.push(post)
}
})
Promise.all(finalListOfPosts).then(function(posts){
var relatedPosts = _.first(_.shuffle(posts), 5)
// MY DATA IS CONSOLE.LOGGED AS I WOULD EXPECT
// BUT HOW DO I RETURN IT?
console.log(relatedPosts)
})
})
})
})
});
Promises work by return value. Just like regular functions. If you then a promise the value you return from the then is the value the outer promise will assume:
var p = Promise.resolve().then(() => { // resolve creates a new promise for a value
return 3; // can also return another promise here, it'll unwrap
});
p.then(alert); //alerts 3
For this, if you have a nested chain (you never have to nest more than 3 levels) - you need to return from it all the way in order to access the value:
return Promise.map(tagList, function(name){ // map is a bluebird utility
return site.tags.findOne({name: name}).posts.sort('date', -1).limit(25).toArray();
}).
then(_.flatten). // flatten the list
filter(function(item){
return item.title !== title; // filter - also a bluebird utility, like Array#filter
}).then(function(posts){
return _.first(_.shuffle(posts), 5);
});
I have a function nested inside a javascript plugin which quite simply checks if something exists inside of an IndexedDB and then ideally I want it to return true or false.
I have assigned the call to the function to a variable (eg var res = $(document).check('name');)
However all I get in the console output if I do a console debug on it is undefined. If I add more console logging into the function it is getting the record correctly and in the right part of the if statement but there is no value being returned.
(function($){
var db, tx, callback, options,
names = {
checkFor: function(name){
//Lightweight copy of above without Thumbs
console.log('In checkfor');
var tx = db.transaction('myDB').objectStore('people').get(name).onsuccess = function(event){
console.debug('res',event);
var res = event.target.result;
if(res.length > 0)
return true;
else
return false;
};
}
}
$.extend($.fn,{
check: function(name){
names.checkFor(name);
});
}(jQuery));
The above is an example, there are many more functions and the plugin is quite lengthily. If anyone could help that would be greatly appreciated!
Thanks!
I have a function in my controller which request dataservice for data and I am trying to add to current scope variable but it is giving me undefined error
$scope.affiliates = d.data;
if (isNaN($scope.affiliate_id)){
var i = 0;
while (i < $scope.affiliates.length){
var affiliate_id = $scope.affiliates[i].affiliate_id.replace(/["']/g, "");
DataService.getAffiliateConversionApiService(affiliate_id).then(function(apiData){
$scope.affiliates[i].apiData = apiData;//ERROR IN HERE
});
i++;
}
}
TypeError: $scope.affiliates[i] is undefined
I have also tried returning data from dataService and set it outside but it always returns empty.
How can i resolve this?
Don't forget that getAffiliateConversionApiService is returning a promise (which means it is an async operation) therefore your while block will execute for every possible i value before you even get the result from getAffiliateConversionApiService.
Let's imagine that $scope.affiliates.length is 6. When your callback code inside the then is executed, your i will be 7.
One solution is to use angular.forEach instead of the while. However, if you still want to use the while you will need to store the i value in another variable:
while (i < $scope.affiliates.length){
var index = i;
var affiliate_id = $scope.affiliates[i].affiliate_id.replace(/["']/g, "");
DataService.getAffiliateConversionApiService(affiliate_id).then(function(apiData){
$scope.affiliates[index].apiData = apiData;
});
i++;
}
This is not really an answer, but here is some debugging that should help you out.
Also, I switched from using a while loop to using angular's forEach
$scope.affiliates = d.data;
if (isNaN($scope.affiliate_id)){
console.log($scope.affiliates); // what does this return?
angular.forEach($scope.affiliates, function(value, index){
console.log(value); // output the affiliate, if any
var affiliate_id = value.affiliate_id.replace(/["']/g, "");
DataService.getAffiliateConversionApiService(affiliate_id).then(function(apiData){
console.log(apiData); // see what is returned
$scope.affiliates[index].apiData = apiData; // still an error?
});
});
}
I saw many post like here and here regarding this type error but in my case i
have an object in function like below code and i get this error "Can't execute code from a freed script "
function updateLetterOfClaim(thisletterClaimID) {
var updatedLetter = {};
updatedLetter.new_breaches = top.opener.selected_breach.join(",");//from this line in ie7 i get this error
updatedLetter.new_causation = top.opener.selected_causation.join(",");
updatedLetter.new_chronology = top.opener.chronology_records.join(",");
updatedLetter.new_Injuries = top.opener.damage_records;
}
after see many link and blogs my result come by applying following code
my problem is which i found i apply join() in objects but it apply in array so first i make
my objects in array then apply join ....
function updateLetterOfClaim(thisletterClaimID) {
var updatedLetter = {};
updatedLetter.new_breaches =joinobjects(top.opener.selected_breach);
updatedLetter.new_causation =joinobjects( top.opener.selected_causation);
updatedLetter.new_chronology =joinobjects(top.opener.chronology_records);
updatedLetter.new_Injuries = joinobjects(top.opener.damage_records);
}
function joinobjects(object){
return $.map(object, function(v){
return v;
}).join(', ');
}