Accessing values of promises in an array - javascript

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
});
}

Related

Data for factory coming too late–Angular.js

I had similar code to this working before and for some reason, this one chunk is not. It's a simple idea; call the function, it calls an $http-based factory to get data and return an array of that data. First is the factory:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
data.get('sensors').then(function (results) {
var sensors = [];
for(var i = 0; i < results.sensors.length; i++) {
sensors.push({
'id' : Number(results.sensors[i][0]),
'name' : results.sensors[i][1],
'type' : results.sensors[i][2],
'device' : results.sensors[i][3],
'unit' : results.sensors[i][4],
'parent' : Number(results.sensors[i][5]),
'latitude' : Number(results.sensors[i][6]),
'longitude' : Number(results.sensors[i][7])
});
}
return sensors;
});
}
};
}]);
Which uses the following data service:
app.factory('data', ['$http', function ($http) {
var serviceBase = '/api/';
var obj = {};
obj.get = function (q) {
return $http.get(serviceBase + q).then(function (results) {
return results.data;
});
};
return obj;
}]);
And for the call to the factory, I've tried getSensors, getSensors.get, and getSensors.get(), even though I know some of those are wrong. I have also tried not wrapping this code in a function, using a function defined above the return, and returned the array in no object (how it is working in my other function, even though I don't think it is best practice, it's just simpler). I've even swapped the for loop for a forEach.
Basically, the returned value from the getSensors factory always happens before the $http call completes from my use of console debugging, even if the code is in the callback function and after that loop. I am at a loss as to how this is possible after spending 2 hours looking at it and rewriting this code. I maybe am simply missing something obvious or am violating some aspect of Angular.
The critical part you are missing is to return the promise created by the .then() in your getSensors.get() method:
app.factory('getSensors', ['data', function (data) {
return { get : function() {
return data.get('sensors').then(function (results) {
var sensors = [];
/* for loop (same as before) goes here */
return sensors;
});
}
};
}]);
Then to use it, just inject the getSensors service as a dependency, and call
getSensors.get().then(function (sensors) {
/* do something with sensors array */
});

Returning Value from nested Promise.alls

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);
});

Factory has two methods, one is $http method with $promise - how to reference the other method from within the first one's success function?

In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.

Getting a Filter to handle a Promised Service

I have already started to rework this code to operate synchronously, but out of curiosity and a desire to support both means, I need some help understanding how to get a filter to jive with a promise. As some other posts mention a filter seems to just resolve to {} from a promise.
Basic Pattern
Here's a breakdown:
Define a service in the module that returns a promise instead of an object
module.factory('promisedSvc', ['$http', function($http) {
var httpPromise = null,
servicePromise = null,
service = {},
dataSet = {};
var httpPromise = $http.get('somedata.json').success(function(data) {
dataSet = data;
});
servicePromise = httpPromise.then(function(){
service.getData = function(key) {
return dataSet[key];
};
service.addData = function(key, value) {
dataSet[key] = value;
};
return service;
});
/*
In actuality I proxied the service methods onto the promise because
I didn't want consumers of the service to have to deal with it being
a promise. There is the caveat of setting properties on a class I
don't own (property collisions), a risk I'm okay taking, but YMMV
Commented out proxies
servicePromise.getData = function(key) {
return this.then(function(svc){
return svc.getData(key);
});
};
servicePromise.addData = function(key, value) {
this.then(function(svc){
svc.addData(key, value);
});
};
*/
return servicePromise;
}]);
Controllers can handle this promisedSvc fine, you just get the promise injected into the controller and then use the then function on the promise to wrap the setting of a $scope property to the function call on the eventual service object: getData(key) or setData(key, value). Alternately you can just treat it as normal if you proxied the functions onto the promise like in the commented out block.
Filters do not seem to inherently handle promises like $scope does. I am looking for a way to get the filter to inject the promisedSvc and be able to call getData(key) without it resolving to {} because the promise has not resolved yet. Below is an example of what does not work:
module.filter('svcData', ['promisedSvc', function(promisedSvc) {
return function(input) {
return promisedSvc.then(function(svc) {
var value = svc.getData(input);
return value;
});
};
}]);
So is there a way to write the filter to be able to resolve the value?
Use Case
That is the simplified pattern of what I am trying to achieve. For those curious, my actual use case is to pre-fetch i18n/l10n resource bundle information so I can localize all the text in my application. The pre-fetch could all be in the Javascript environment (attached to some already loaded global or in a provider), but we also have scenarios with database-stored Resource Bundles so I needed a version of code that can pre-fetch all the information from the server via AJAX.
It's not exactly what I'm looking for, but at least to document a workaround:
It's possible to use a function on the $scope instead of a filter.
module.factory('promisedSvc', ['$http', '$rootScope', function($http, $rootScope) {
var httpPromise = null,
servicePromise = null,
service = {},
dataSet = {};
var httpPromise = $http.get('somedata.json').success(function(data) {
dataSet = data;
});
servicePromise = httpPromise.then(function(){
service.getData = function(key) {
return dataSet[key];
};
service.addData = function(key, value) {
dataSet[key] = value;
};
//Here is the addition to setup the function on the rootScope
$rootScope.svcData = function(key) {
return service.getData(key);
};
return service;
});
return servicePromise;
}]);
And then in a template instead of {{ 'key1' | svcData }} you would use {{ svcData('key1') }}
I tested that if you delay the promises resolution (for example setup a wait in the $http.success) that the impact is the page loads, but the values from the svcData function will only populate into the template once the promises resolve.
Still would be nice to accomplish the same with a filter if possible.

AngularJS : Asynchronously initialize filter

I'm having trouble trying to initialize a filter with asynchronous data.
The filter is very simple, it needs to translate paths to name, but to do so it needs a correspondance array, which I need to fetch from the server.
I could do things in the filter definition, before returning the function, but the asynchronous aspect prevents that
angular.module('angularApp').
filter('pathToName', function(Service){
// Do some things here
return function(input){
return input+'!'
}
}
Using a promise may be viable but I don't have any clear understanding on how angular loads filters.
This post explains how to achieve such magic with services, but is it possible to do the same for filters?
And if anyone has a better idea on how to translate those paths, I'm all ears.
EDIT:
I tried with the promise approch, but something isn't right, and I fail to see what:
angular.module('angularApp').filter('pathToName', function($q, Service){
var deferred = $q.defer();
var promise = deferred.promise;
Service.getCorresp().then(function(success){
deferred.resolve(success.data);
}, function(error){
deferred.reject();
});
return function(input){
return promise.then(
function(corresp){
if(corresp.hasOwnProperty(input))
return corresp[input];
else
return input;
}
)
};
});
I'm not really familliar with promises, is it the right way to use them?
Here is an example:
app.filter("testf", function($timeout) {
var data = null, // DATA RECEIVED ASYNCHRONOUSLY AND CACHED HERE
serviceInvoked = false;
function realFilter(value) { // REAL FILTER LOGIC
return ...;
}
return function(value) { // FILTER WRAPPER TO COPE WITH ASYNCHRONICITY
if( data === null ) {
if( !serviceInvoked ) {
serviceInvoked = true;
// CALL THE SERVICE THAT FETCHES THE DATA HERE
callService.then(function(result) {
data = result;
});
}
return "-"; // PLACEHOLDER WHILE LOADING, COULD BE EMPTY
}
else return realFilter(value);
}
});
This fiddle is a demonstration using timeouts instead of services.
EDIT: As per the comment of sgimeno, extra care must be taken for not calling the service more than once. See the serviceInvoked changes in the code above and the fiddles. See also forked fiddle with Angular 1.2.1 and a button to change the value and trigger digest cycles: forked fiddle
EDIT 2: As per the comment of Miha Eržen, this solution does no logner work for Angular 1.3. The solution is almost trivial though, using the $stateful filter flag, documented here under "Stateful filters", and the necessary forked fiddle.
Do note that this solution would hurt performance, as the filter is called each digest cycle. The performance degradation could be negligible or not, depending on the specific case.
Let's start with understanding why the original code doesn't work. I've simplified the original question a bit to make it more clear:
angular.module('angularApp').filter('pathToName', function(Service) {
return function(input) {
return Service.getCorresp().then(function(response) {
return response;
});
});
}
Basically, the filter calls an async function that returns the promise, then returns its value. A filter in angular expects you to return a value that can be easily printed, e.g string or number. However, in this case, even though it seems like we're returning the response of getCorresp, we are actually returning a new promise - The return value of any then() or catch() function is a promise.
Angular is trying to convert a promise object to a string via casting, getting nothing sensible in return and displays an empty string.
So what we need to do is, return a temporary string value and change it asynchroniously, like so:
JSFiddle
HTML:
<div ng-app="app" ng-controller="TestCtrl">
<div>{{'WelcomeTo' | translate}}</div>
<div>{{'GoodBye' | translate}}</div>
</div>
Javascript:
app.filter("translate", function($timeout, translationService) {
var isWaiting = false;
var translations = null;
function myFilter(input) {
var translationValue = "Loading...";
if(translations)
{
translationValue = translations[input];
} else {
if(isWaiting === false) {
isWaiting = true;
translationService.getTranslation(input).then(function(translationData) {
console.log("GetTranslation done");
translations = translationData;
isWaiting = false;
});
}
}
return translationValue;
};
return myFilter;
});
Everytime Angular tries to execute the filter, it would check if the translations were fetched already and if they weren't, it would return the "Loading..." value. We also use the isWaiting value to prevent calling the service more than once.
The example above works fine for Angular 1.2, however, among the changes in Angular 1.3, there is a performance improvement that changes the behavior of filters. Previously the filter function was called every digest cycle. Since 1.3, however, it only calls the filter if the value was changed, in our last sample, it would never call the filter again - 'WelcomeTo' would never change.
Luckily the fix is very simple, you'd just need to add to the filter the following:
JSFiddle
myFilter.$stateful = true;
Finally, while dealing with this issue, I had another problem - I needed to use a filter to get async values that could change - Specifically, I needed to fetch translations for a single language, but once the user changed the language, I needed to fetch a new language set. Doing that, proved a bit more tricky, though the concept is the same. This is that code:
JSFiddle
var app = angular.module("app",[]);
debugger;
app.controller("TestCtrl", function($scope, translationService) {
$scope.changeLanguage = function() {
translationService.currentLanguage = "ru";
}
});
app.service("translationService", function($timeout) {
var self = this;
var translations = {"en": {"WelcomeTo": "Welcome!!", "GoodBye": "BYE"},
"ru": {"WelcomeTo": "POZHALUSTA!!", "GoodBye": "DOSVIDANYA"} };
this.currentLanguage = "en";
this.getTranslation = function(placeholder) {
return $timeout(function() {
return translations[self.currentLanguage][placeholder];
}, 2000);
}
})
app.filter("translate", function($timeout, translationService) {
// Sample object: {"en": {"WelcomeTo": {translation: "Welcome!!", processing: false } } }
var translated = {};
var isWaiting = false;
myFilter.$stateful = true;
function myFilter(input) {
if(!translated[translationService.currentLanguage]) {
translated[translationService.currentLanguage] = {}
}
var currentLanguageData = translated[translationService.currentLanguage];
if(!currentLanguageData[input]) {
currentLanguageData[input] = { translation: "", processing: false };
}
var translationData = currentLanguageData[input];
if(!translationData.translation && translationData.processing === false)
{
translationData.processing = true;
translationService.getTranslation(input).then(function(translation) {
console.log("GetTranslation done");
translationData.translation = translation;
translationData.processing = false;
});
}
var translation = translationData.translation;
console.log("Translation for language: '" + translationService.currentLanguage + "'. translation = " + translation);
return translation;
};
return myFilter;
});

Categories