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);
});
Related
Regular promises have the beloved .then() and .catch() functions.
When promising to retrieve an object that itself has properties that return promises we find chains of promises such as the following:
require("clientside-view-loader")
.then((view)=>
return view.load("clientside-view-modal-login_signup");
})
.then((compiler)=>{
return compiler.generate()
})
.then((modal)=>{
document.body.appendChild(modal);
modal.show("login");
})
This is UGLY!
How can we modify a promise to attach a custom property so that we can convert the above into the following?
require("clientside-view-loader")
.load("clientside-view-modal-login_signup")
.generate()
.then((modal)=>{
document.body.appendChild(modal);
modal.show("login");
})
note, these examples use the clientside-require require and not the nodejs require
How can we modify a promise to attach a custom property so that we can convert the above into the following?
You don't modify promises at all. You just implement the builder pattern for the above promise chain.
class ClientSideViewLoader {
constructor(p = Promise.resolve()) {
this.promise = p;
}
static init() {
return new this(require("clientside-view-loader"));
}
load(x) {
return new this.constructor(this.promise.then(view =>
view.load(x)
));
}
generate() {
return new this.constructor(this.promise.then(compiler =>
compiler.generate()
));
}
then(...args) {
return this.promise.then(...args);
}
}
ClientSideViewLoader.init()
.load("clientside-view-modal-login_signup")
.generate()
.then(modal => {
document.body.appendChild(modal);
modal.show("login");
})
No need to do anything complicated like subclassing Promise. If you want, you can also dynamically generate all these methods.
This is UGLY!
Well, if you were looking for beautiful promise code, you would simply use modern async/await syntax instead of then callbacks:
const view = await require("clientside-view-loader");
const compiler = await view.load("clientside-view-modal-login_signup");
const modal = await compiler.generate();
document.body.appendChild(modal);
modal.show("login");
Your initial code can be made shorter and more readable simply by using different syntax for your arrow functions. These two rules of arrow function syntax are relevant:
parentheses are optional around the only argument of single-argument functions
single-statement functions that return a value can have the {} and the return removed
Thus, you could write your code like this, with the short form view => … instead of (view) => { return …; }:
require("clientside-view-loader")
.then(view => view.load("clientside-view-modal-login_signup"))
.then(compiler => compiler.generate())
.then(modal => {
document.body.appendChild(modal);
modal.show("login");
});
If you know the properties you wish to add in advance you can simply append a property to the promise like you would any other object:
view_loader.load = function(path){
return this.then((view_loader)=>{
return view_loader.load(path)
})
}
view_loader.load(...) // now works!
Here's a function that does this for a dynamic set of properties:
function modify_orig_promise(original_promise, properties_to_append){
var blacklist = ["then", "catch", "spread"];
var function_keys = Object.keys(properties_to_append);
for(var i = 0; i < function_keys.length; i++){
var function_key = function_keys[i];
if(blacklist.indexOf(function_key) > -1) {
console.warn("properties_to_append in require(__, {functions : {} }) included a blacklisted function name : `"+key+"`. skipping this property.")
} else {
var requested_function = properties_to_append[function_key];
original_promise[function_key] = requested_function; // append the function to the promise
}
}
return original_promise;
}
Then
var properties_to_append = {
load : function(path){
return this.then((view_loader)=>{ return view_loader.load(path)})
}
}
modified_require = modify_orig_promise(require("clientside-view-loader"), properties_to_append);
modified_require.load("clientside-view-modal-login_signup") // Works
If you dont know the properties in advance (e.g., the properties are determined from a promise) you'll need to use a proxy that waits until that promise resolves to respond. This is answered here: How to add properties to a promise asynchronously?
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
})
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 trying to create an array from http requests that holds a name property and two promises: one as an array and the other as an object. I'm able to get the info I need using this approach but I'm not able to access it to display it in the scope of the html. For instance, when I log out the array, "people" I get an array of objects (looking like: [Object, Object, Object]) and I have to expand a bunch of things to see the actual values of each object so that "person.skills" would really have to be "person.skills.$$state.value". Also, on the page, {{person.name}} will show up but the other two are just empty objects that look like this: {}. So how can I access the values of the promises so that I can just use {{person.skills}} to show the array?
js
var getPeople = function() {
var named = $q.defer();
$http.get('/getnames').success(function (response) {
named.resolve(response);
});
return named.promise;
};
getPeople().then(function(namesRes) {
var people = [];
names = namesRes;
names.forEach(function(index){
var name = index;
var count = $q.defer();
var skills = $q.defer();
var urls = '/getskillsbyname/' + name;
var urlc = '/getcountbyname/' + name;
$http.get(urls).success(function (response) {
skills.resolve(response);
});
$http.get(urlc).success(function (response) {
count.resolve(response);
});
people.push({name:name, skills:skills.promise, count:count.promise});
});
return people;
}).then(function(people) {
console.log(people);
$scope.people = people;
});
html
<div ng-repeat="person in people">
<p>{{person.name}}</p>
<p>{{person.skills}}</p>
<p>{{person.count}}</p>
</div>
Your method not returning promise correctly, you need to use $q for waiting till all the inner promises get completed.
I have implemented your code by maintaining grand promise variable in the forEach loop, whenever asking skills and couts call are made, it put that call inside $q.all and $q.all promise is moved to grandPromiseArray.
var getPeople = function() {
return $http.get('/getnames');
};
getPeople().then(function(response) {
var people = [];
names = response.data;
grandPromiseArray = [];
names.forEach(function(index) {
var name = index, count = $q.defer(), skills = [],
urls = '/getskillsbyname/' + name, urlc = '/getcountbyname/' + name;
grandPromiseArray.push(
$q.all([$http.get(urls), $http.get(urlc)])
.then(function(response) {
people.push({
name: name,
skills: response[0].data, //response[0] value returned by 1st promise
count: response[1].data //response[1] value returned by 2nd promise
});
})
);
});
return $q.all(grandPromiseArray).then(function() {
return people
});
})
.then(function(people) {
console.log(people);
$scope.people = people;
});
Example for a valid promise chain:
function getSomeData(){
var defer = $q.defer();
$http.get(urls).success(function (response) {
defer.resolve(response);
});
return defer.promise;
}
And then you can access the promise data like:
getSomeData().then(function(data){
console.log(data); // This is the response of the $http request
});
Organize your requests in functions and it looks much cleaner.
$http also returns promise by default, but I recommend to create a service/factory, which doing your requests, then you need the defer.
$http.get(urls).then(function(data){
});
A Promise isn't going to automatically allow you to display your results in Angular.
A Promise is an object, which lets you chain async operations together, by passing in functions which are eventually fired and passed the value.
You can't ask for myPromise.value and expect it to be there, because it's an async process which might take 20ms, 2mins, or might just never come back at all.
In terms of how this is structured, it might be much cleaner and easier to reason about if you broke the data-fetching parts out into a service, and just injected the service into the controller.
I'm not sure which version of Angular you've been working with, but I'm hoping its at least 1.2.
Also, my example is using the native Promise constructor, but I'm sure Angular's version of $q now has everything that's needed, if you aren't using a promise polyfill.
function PersonService (http) {
function getResponseData (response) { return response.data; }
function getURL (url) { return http.get(url).then(getResponseData); }
function makePerson (personData) {
return {
name: personData[0],
skills: personData[1],
count: personData[2]
};
}
var personService = {
getNames: function () { return getURL("/names/"); },
getSkills: function (name) { return getURL("/getskillsbyname/" + name); },
getCounts: function (name) { return getURL("/getcountsbyname/" + name); },
loadPerson: function (name) {
return Promise.all([
Promise.resolve(name),
personService.getSkills(name),
personService.getCount(name)
]).then(makePerson);
},
loadPeople: function (names) {
return Promise.all(names.map(personService.loadPerson));
}
};
return personService;
}
Here's what my person service might look like.
I've written a couple of super-small helper functions that keep getting reused.
Then I've made the service all about getting either people or details about people.
I'm using Promise.all, and passing it an array of promises. When every promise in the array is complete, it returns an array of all of the data returned by the promises. Promise.resolve is used in one spot. It basically returns a promise which succeeds automatically, with the value it was given. That makes it really useful, when you need a promise to start a chain, but you don't have to do anything special, aside from returning a value you already have.
My assumption is both that q now names its methods the same way the spec does, and that Angular's implementation of $q follows the spec as well.
function MyController (personService) {
var controller = this;
controller.people = [];
controller.error = false;
init();
function setPeople (people) {
controller.people = people || [];
}
function handleError (err) {
setPeople([]);
controller.error = true;
}
function init () {
return personService.getNames()
.then(personService.loadPeople)
.then(setPeople)
.catch(handleError);
}
}
My controller now gets really, really simple. It's using the service to get names, and load people, and then it sets them, and it's ready to go. If there was an error, I handle it in whatever way makes sense.
Handling the injection of this stuff is pretty quick and easy:
angular.module("myExample")
.service("PersonService", ["$http", PersonService])
.controller("MyController", ["PersonService", MyController]);
Using this on the page is now painless, as well:
<div ng-controller="MyController as widget">
<ul ng-hide="widget.people.length == 0">
<li ng-repeat="person in widget.people">
<person-details person="person"></person-details>
</li>
</ul>
<div ng-show="widget.error">Sorry, there was an error with your search.</div>
</div>
I think you could probably use $q.all to construct your people:
var p = $q.all({
name: $q.when(name),
skills: $http.get(urls),
count: $http.get(urlc)
});
p.then(function(person) {
people.push(person);
});
$q.all will construct a new promise that gets resolved when all of the input promises resolve. Since we input a map, $q.all will resolve with a map that has the same keys. The values are the resolutions of the corresponding promises. In this case, that's a person so we can then just push it into the people array directly.
This is a slightly naive implementation. Since the calls are asynchronous, there is no guarantee that the order of names will be preserved in people -- but it shouldn't be too hard for you to fix that if it is important.
Create an array of all the promises for the counts and skills. Have broken those requests up into their own functions for readability
Also then() of $http returns a promise object that has a property data
var getPeople = function() {
return $http.get('/getnames').then(function(response) {
return response.data;
});
};
getPeople().then(function(people) {
var promises = [];
people.forEach(function(person) {
// push new requests to promise array
promises.push(getSkills(person));
promises.push(getCount(person))
});
// when all promises resolved return `people`
return $q.all(promises).then(function() {
// return `people` to next `then()`
return people;
});
}).then(function(people) {
console.log(people);
$scope.people = people;
}).catch(function(){
// do something if anything gets rejected
});
function getCount(person) {
var urlc = '/getcountbyname/' + person.name;
return $http.get(urlc).then(function(response) {
person.count = response.data
});
}
function getSkills(person) {
var urls = '/getskillsbyname/' + person.name;
return $http.get(urls).then(function(response) {
person.skills = response.data
});
}
I am new to RxJS and I am trying to write an app that will accomplish the following things:
On load, make an AJAX request (faked as fetchItems() for simplicity) to fetch a list of items.
Every second after that, make an AJAX request to get the items.
When checking for new items, ONLY items changed after the most recent timestamp should be returned.
There shouldn't be any state external to the observables.
My first attempt was very straight forward and met goals 1, 2 and 4.
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.map(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`, but
// I am not yet tracking the `modifiedSince` timestamp yet so all items will always be returned
return fetchItems();
});
Now I'm excited, that was easy, it can't be that much harder to meet goal 3...several hours later this is where I am at:
var modifiedSince = null;
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
This solves goal 3, but regresses on goal 4. I am now storing state outside of the observable.
I'm assuming that global modifiedSince and the .do() block aren't the best way of accomplishing this. Any guidance would be greatly appreciated.
EDIT: hopefully clarified what I am looking for with this question.
Here is another solution which does not use closure or 'external state'.
I made the following hypothesis :
fetchItems returns a Rx.Observable of items, i.e. not an array of items
It makes use of the expand operator which allows to emit values which follow a recursive relationship of the type x_n+1 = f(x_n). You pass x_n+1 by returning an observable which emits that value, for instance Rx.Observable.return(x_n+1) and you can finish the recursion by returning Rx.Observable.empty(). Here it seems that you don't have an ending condition so this will run forever.
scan also allows to emit values following a recursive relationship (x_n+1 = f(x_n, y_n)). The difference is that scan forces you to use a syncronous function (so x_n+1 is synchronized with y_n), while with expand you can use an asynchronous function in the form of an observable.
Code is not tested, so keep me updated if this works or not.
Relevant documentation : expand, combineLatest
var modifiedSinceInitValue = // put your date here
var polling_frequency = // put your value here
var initial_state = {modifiedSince: modifiedSinceInitValue, itemArray : []}
function max(property) {
return function (acc, current) {
acc = current[property] > acc ? current[property] : acc;
}
}
var data$ = Rx.Observable.return(initial_state)
.expand (function(state){
return fetchItem(state.modifiedSince)
.toArray()
.combineLatest(Rx.Observable.interval(polling_frequency).take(1),
function (itemArray, _) {
return {
modifiedSince : itemArray.reduce(max('updatedAt'), modifiedSinceInitValue),
itemArray : itemArray
}
}
})
You seem to mean that modifiedSince is part of the state you carry, so it should appear in the scan. Why don-t you move the action in do into the scan too?. Your seed would then be {modifiedSince: null, itemArray: []}.
Errr, I just thought that this might not work, as you need to feed modifiedSince back to the fetchItem function which is upstream. Don't you have a cycle here? That means you would have to use a subject to break that cycle. Alternatively you can try to keep modifiedSince encapsulated in a closure. Something like
function pollItems (fetchItems, polling_frequency) {
var modifiedSince = null;
var data$ = Rx.Observable.interval(polling_frequency)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
return data$;
}
I have to run out to celebrate the new year, if that does not work, I can give another try later (maybe using the expand operator, the other version of scan).
How about this:
var interval = 1000;
function fetchItems() {
return items;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(); })
.filter(function(x) {return x.lastModified > Date.now() - interval}
.skip(1)
.startWith(fetchItems());
That should filter the source only for new items, plus start you off with the full collection. Just write the filter function to be appropriate for your data source.
Or by passing an argument to fetchItems:
var interval = 1000;
function fetchItems(modifiedSince) {
var retVal = modifiedSince ? items.filter( function(x) {return x.lastModified > modifiedSince}) : items
return retVal;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(Date.now() - interval); })
.skip(1)
.startWith(fetchItems());