I'm having trouble understanding promises. I have a function $scope.startJob that when called, it calls areThereAvailableSegments in the SegmentsService. The idea is that areThereAvailableSegments should return true or false, so that I can handle this behavior in the controller, but not sure how to do this.
areThereAvailableSegments calls getSegments(), which requests text segments from the database. getSegments returns a promise, and I'm trying to handle this promise with .then in areThereAvailableSegments, but not sure how to pass to the controller.
Here's my Segments factory, that returns the $resource object, segmentsfactory.js:
angular.module('appApp')
.factory('SegmentsFactory', function ($resource) {
return $resource('http://localhost:3000/api/v1/segments/:id');
});
Here's my Segments service, segmentsservice.js:
angular.module('appApp')
.service('SegmentsService', function (SegmentsFactory) {
// gets segments from mongo
this.getSegments = function(jobId) {
return SegmentsFactory.query({ job_id: jobId }).$promise;
};
// should return true/false if there are available segments
this.areThereAvailableSegments = function(jobId) {
var dump = [];
// queries for all segments
this.getSegments(jobId)
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
return true;
} else {
return false;
}
});
};
});
And here's my controller, main.js:
$scope.startJob = function() {
if (SegmentsService.areThereAvailableSegments(jobId)) {
console.log('there are available segments, then trigger texteditor');
} else {
console.log('no segments to translate');
}
};
When executing, console shows "no segments to translate", because SegmentsService is not returning true or false.
How to solve this?
the function areThereAvailibleSegments should return a promise as well.
And then in your controller you have a then case and have your if-else there.
this.areThereAvailableSegments = function(jobId) {
var dump = [];
// queries for all segments
return this.getSegments(jobId) //Return here
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
return true;
} else {
return false;
}
});
};
since your success function returns true or false the promise from areThereAvailibleSegments-function will have that boolean.
Then in you controller:
SegmentsService.areThereAvailableSegments(jobId).then(function(available){
if (available) {
console.log('there are available segments, then trigger texteditor');
} else {
console.log('no segments to translate');
}
});
Theres no reason to have both the service and factory. Remove the factory and move that code into the service. As for your problem, you will have to use the $q module to return a promise. https://docs.angularjs.org/api/ng/service/$q
this.areThereAvailableSegments = function(jobId) {
var dump = [];
var deferred = $q.deferred;
// queries for all segments
this.getSegments(jobId)
.then(function(segments) {
// dumps segments status in a temp array
for (var i = 0; i < segments.length; i++) {
dump.push(segments[i].status);
}
// returns true if there is an 'available' in the temp array
if (dump.indexOf('available') >= 0) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
return deferred.promise;
};
You will then have to deal with the promise in the controller.
I would set it up like this...complete the promise in your factory and then call the data in the controller.
In your services js:
.factory('SegmentsFactory', function ($resource) {
var APIRequest = {};
APIRequest.getSegments = function(id){
segmentsAPI = $resource('http://localhost:3000/api/v1/segments/'+id'', ;
return segmentAPI.get().$promise.then(function(segments){
console.log(segments);
return segments;
});
};
return APIRequest;
}]);
And then in your controller:
var dump = [];
$scope.startJob = function() {
if (SegmentsService.areThereAvailableSegments) {
APIRequest.getSegments($scope.id).then(function(data){
if (data.error){
//error stuff here
} else {
if (data.segments.length > 0){
for(var i=0; i < segments.length; i++){
console.log(segments);
dump.push(segments[i].status);
}
};
Related
I have an items array that I would be getting from localStorage.
var items = JSON.parse($window.localStorage.selectedResources)['server'];
var arr = [];
var idsArray = [];
angular.forEach(items, function (item) {
idsArray.push(item.id);
});
Then I fire an API call ...
//Make the API call
ds.getBillInfo(idsArray)
.then(function(response){
var serversList = [];
for (var key in response) {
// iterate over response
The problem is if the items array is empty, so does the idsArray.
Then the error says Cannot read property 'then' of undefined.
What I want to do is even if the idsArray is empty , I want lines to execute inside the then block thinking as there is no promise.
How can I do that ?
EDIT
If I do $q.all([ds.getBillInfo(idsArray)]) then there is no error.
The getBillInfo() looks like:
this.getBillInfo = function(idsArray){
if(!idsArray.length) return;
var segmentUrl = '';
for(var i =0;i<idsArray.length;i++){
if(i != (idsArray.length-1))
segmentUrl += 'ids='+idsArray[i]+'&';
else
segmentUrl += 'ids='+idsArray[i];
}
return HttpWrapper.send('/api/bill?bill=t&'+segmentUrl, {"operation": 'GET'});
};
In getBillInfo wrap your logic with new Promise and on empty array resolve it.
Something like:
self.getBillInfo = function(array){
var deferred = $q.defer();
if(array.length == 0){
deferred.resolve([]); // return empty list
}
else{
var segmentUrl = '';
for(var i =0;i<idsArray.length;i++){
if(i != (idsArray.length-1))
segmentUrl += 'ids='+idsArray[i]+'&';
else
segmentUrl += 'ids='+idsArray[i];
}
HttpWrapper.send('/api/bill?bill=t&'+segmentUrl, {"operation": 'GET'})
.then(function (response) {
deferred.resolve(response.data);
}
, function (error) {
deferred.reject(error);
});
}
return deferred.promise;
}
[EDIT]
Regards to #JC Ford point, since HttpWrapper returns Promise we can write above logic with different way as:
self.getBillInfo = function(array){
if(array.length == 0){
return $q.resolve([]); // return empty list;
}
else{
var segmentUrl = '';
for(var i =0;i<idsArray.length;i++){
if(i != (idsArray.length-1))
segmentUrl += 'ids='+idsArray[i]+'&';
else
segmentUrl += 'ids='+idsArray[i];
}
return HttpWrapper.send('/api/bill?bill=t&'+segmentUrl, {"operation": 'GET'});
}
}
inject the $q service so that it is accessible in getBillInfo(). You can then wrap a value in $q.resolve() to make a promise that returns that value. (That value could even be another promise.) So if your getBillInfo() function sometimes needs to return early without a value, just return an empty $q.resolve() instead to ensure you're always returning a promise.
this.getBillInfo = function(idsArray){
//This returns undefined and causes your error.
if(!idsArray.length) return;
//This returns a promise that resolves immediately and executes your .then() handler.
if(!idsArray.length) return $q.resolve();
//This returns a promise that rejects immediately and executes your .catch() handler
if(!idsArray.length) return $q.reject();
var segmentUrl = '';
for(var i =0;i<idsArray.length;i++){
if(i != (idsArray.length-1))
segmentUrl += 'ids='+idsArray[i]+'&';
else
segmentUrl += 'ids='+idsArray[i];
}
return HttpWrapper.send('/api/bill?bill=t&'+segmentUrl, {"operation": 'GET'});
};
I have a problem with my promise after a foreach,
I would like to remove the object which has no parent from my database.
I read this post and I tried many things, for example this:
controller
project.getAll().then(function(results) {
return $scope.array_projects = results;
}).then(function(array_projects) {
$scope.results = [];
console.log(array_projects);
angular.forEach(array_projects, function(result) {
console.log(result);
var project = new Project(result);
var promise = project.getOne(result.ParentId);
$scope.result.push(promise);
promise.then(function(results) {
return results.length ? project:null;
});
// if(result.ParentId !== null) {
// console.log('ParentId');
// var promise = project.getOne(result.ParentId).then(function(results) {
// //console.log(results);
//
// if (results.length === 0){
// console.log('remove children');
// } else {
// $scope.results.push(project);
// console.log('parent exist, children added!');
//
// }
//
// });
//
// }
});
// after forEach
$q.all($scope.results).then(function(results){
console.log($scope.results);
// result is an array of mixed Project object or null value, just remove the null value and you're fine :)
var trueResults = [];
for(var i = 0; i < results.length; i++){
if(results[i]){
trueResults.push(results[i]);
}
}
// now use trueResults
console.log(trueResults);
})
});
model
var Project = function (properties) {
// Model
this.description = null;
this.file = null;
this.name = null;
this.ParentId = null;
this.path = null;
angular.extend(this, properties);
};
// With service
// ------------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------------
Project.prototype.getAll = function () {
return ProjectService.getAll();
};
Project.prototype.getOne = function (id) {
return ProjectService.getOne(id);
};
project.service.js
this.getAll = function (params) {
var projects = [];
return db.selectAll('projects').then(function(results) {
for(var i=0; i < results.rows.length; i++){
projects.push(results.rows.item(i));
}
return projects;
});
};
this.getOne = function (id) {
var project = [];
console.log(id);
return db.select('projects', {
"id": id
}).then(function(results) {
for(i=0; i < results.rows.length; i++){
project.push(results.rows.item(i));
}
return project;
})
};
But my console.log($scope.results); show '[]'
In my BDD I have :
id/id/ParentId/name/file/date
5 5 test crm-site.net /release.txt 2016-04-26 08:43:17
6 6 5 test2 crm-site.net /release.txt 2016-04-26 08:43:19
7 7 test3 crm-site.net /release.txt 2016-04-26 08:43:20
8 8 7 test4 crm-site.net /release.txt 2016-04-26 08:43:21
It seems like you are misunderstanding how to use $q.all.
Each time you call a project.getOne, you don't know when the result will come as it is asynchonious.
If you want to use the $q.all, you should push all the project.getOne in an array and $q.all(myArray). It is normal that your code only show [] because the .then aren't called yet and the push method haven't been called yet when you go through the console.
You wasn't so far of what you were trying just update your code with :
// in the foreach
if(result.ParentId){
var promise = project.getOne(result.ParentId);
$scope.result.push(promise);
promise.then(function(results) {
return results.length ? project:null;
}
}
// after forEach
$q.all($scope.results).then(function(results){
// result is an array of mixed Project object or null value, just remove the null value and you're fine :)
var trueResults = [];
for(var i = 0; i < results.length; i++){
if(results[i]){
trueResults.push(results[i]);
}
}
// now use trueResults
})
The explanation is that $q.all take an array of promise not objects, he can't wait that you fill an array, because he can't know what criteria makes you're array "ready" unlike a promise where the resolve will be called.
I am recovering all data in allEnquetes. I just find the requested this list, but an error occurs because the allEnquetes returns a promise.
.factory('enquetesAPI', function($http,myConfig){
return {
allEnquete: function(){
return $http.get(myConfig.remote + '/lists.asp?acao=list-enquete&codadmin='+myConfig.codadmin);
},
getEnquete: function(enqueteId) {
for (var i = 0; i < this.allEnquete().length; i++) {
if (this.allEnquete()[i].codigo === parseInt(enqueteId)) {
return this.allEnquete()[i];
}
}
return this.allEnquete()
}
};
})
This is the result when I console.log ( this.allEnquete )
allEnquetes returns a promise so you just can't loop through that. Check below code. I have not tested it. but this should give you some idea on how you should ideally be doing this.
.factory('enquetesAPI', function($http, $q, myConfig){
return {
allEnquete: function(){
console.log('called');
return $http.get(myConfig.remote + '/lists.asp?acao=list-enquete&codadmin='+myConfig.codadmin);
},
getEnquete: function(enqueteId) {
var deferred = $q.defer();
this.allEnquete().then(function(enquetes){
var returnVal = enquetes;
for (var i = 0; i < enquetes.length; i++) {
if (this.allEnquete()[i].codigo === parseInt(enqueteId)){
returnVal = enquetes[i];
}
}
deferred.resolve(returnVal);
}, function(errorResponse){
deferred.reject(errorResponse);
});
return deferred.promise;
}
};
});
and this is how you should be using your service.
enquetesAPI.getEnquete(id).then(enquetes){
// do whatever you need to do
}
I am trying to retrieve data for use in a service that can be used throughout my app. The problem is that I can't get the data to be resolved in time for the routine that uses it. Here is the routine that uses it:
function getTranslation(lookup, language) {
var i = 0;
if (vm.translations == null) {
//vm.translations = getAllTranslations();
dataService.getTranslations()
.then(function (data) {
vm.translations = data;
});
}
var len = vm.translations.length;
for (var i=0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
return null;
}
Here is the calling method within that service:
function getAllTranslations() {
return vm.translations = dataService.getTranslations()
.then(function (data) {
vm.translations = data;
return vm.translations;
});
}
And here is the method in the dataService:
function getTranslations() {
return $http.get('/api/labeltext')
.then (getTranslationComplete)
.catch(getTranslationFailed);
function getTranslationComplete(response) {
var deferred = $q.defer();
return response.data;
}
function getTranslationFailed(error) {
alert("XHR failed for frequent pawner report: " + error.responseText);
}
}
I am still learning angularjs and want to be able to populate the data in the service and then call it from other controllers. However, when I get to my for loop the array is empty and only gets populated after its completed.
That is because the promise will not be resolved before your for loop fires. By placing the loop within the .then(), you will have access to the response and your loop values will be defined. This is not DRY since there would be code duplication if you add an else to the function and would have to add in the same loop code. For that, I would refactor the loop into an external function and just call it from within the proper areas of getTranslation().
function getTranslation(lookup, language) {
var i = 0;
if (vm.translations == null) {
//vm.translations = getAllTranslations();
dataService.getTranslations()
.then(function (data) {
vm.translations = data;
var len = vm.translations.length;
for (var i=0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
});
}
return null;
}
Since your data originates from a promise ($http), all of your subsequent code that needs to access that data has to be within a then function.
angular.controller('myController', function(dataService) {
var translationsPromise;
/**
* Caches translations from /api/labeltext and performs a lookup
* #param lookup
* #param language
*/
function getTranslation(lookup, language) {
if (translationsPromise == null) {
translationsPromise = dataService.getTranslations()
}
translationsPromise.then(function(data) {
vm.translations = data;
var len = vm.translations.length;
for (var i = 0; i < len; i++) {
if (vm.translations[i].labelName == lookup) {
if (language == "English") {
return vm.translations[i].english;
} else {
if (language == "Spanish") {
return vm.translations[i].espanol;
}
}
}
}
});
}
getTranslation('Some Label', 'English').then(function(translation) {
// The translation that was found is accessible in this block
});
});
When data is asynchronously derived, it's always better to cache a promise rather than the data. Thus, should another request for the same data be made before the original promise is resolved, then that promise can be retrieved from cache, and returned or otherwise exploited. The same is not true of cached async data, which is not guaranteed to have arrived when another request is made.
function getTranslation(lookup, language) {
if (!vm.translationsPromise) {
vm.translationsPromise = dataService.getTranslations();
}
return vm.translationsPromise.then(function (data) {
var lang = {'English':'english', 'Spanish':'espanol'};
return data.reduce(function(str, item) {
return (str !== '') ? str : (item.labelName == lookup) ? item[lang[language]] : str;
}, '');
});
}
I have an array of tags which may contain up to 3 items.
Next I pass tags into my getTagQuotes function and am trying to set a promise variable to it. Right now I'm getting promise is undefined.
// If there are tags, the wait for promise here:
if (tags.length > 0) {
var promise = getTagQuotes(tags).then(function() {
console.log('promise =',promise);
for (var i=0; i<tweetArrayObjsContainer.length; i++) {
chartObj.chartData.push(tweetArrayObjsContainer[i]);
}
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
});
}
else {
chartDirective = ScopeFactory.getScope('chart');
chartDirective.nvd3.drawChart(chartObj.chartData);
}
^ The goal above is to make sure that arrays in tweetArrayObjsContainer have been filled and pushed into chartObj.chartData before I draw my nvd3 chart.
Below is my getTagQuotes function which does another loop through the tags (up to 3) and calls another service GetTweetVolFactory.returnTweetVol which makes the actual API call to retrieve data.
I have code which checks if the we're on the final loop step, then calls the formatTagData function.
Inside formatTagData is where I fill the tweetArrayObjsContainer.
// Check for and GET tag data:
////////////////////////////////////////////////////////////////////
function getTagQuotes(tags) {
console.log('getTagQuotes called with',tags);
var deferred = $q.defer(); // <- setting $q.defer here
var url = 'app/api/social/twitter/volume/';
for (var i=0; i<tags.length; i++) {
var loopStep = i;
rawTagData = [];
GetTweetVolFactory.returnTweetVol(url+tags[i].term_id)
.success(function(data, status, headers, config) {
rawTagData.push(data);
if (loopStep === (rawTagData.length - 1)) {
// formatTagData fills the tweetArrayObjsContainer with data:
formatTagData(rawTagData);
deferred.resolve(); // <-- last step, so resolve
return deferred.promise;
}
})
.error(function(data, status) {
return 'error in returning tweet data';
});
}
function formatTagData(rawData) {
tweetArrayObjsContainer = [];
for (var i=0; i<rawData.length; i++) {
var data_array = [];
var loopNum = i;
_(rawData[i].frequency_counts).reverse().value();
for (var j=0; j<rawData[loopNum].frequency_counts.length; j++) {
var data_obj = {};
rawData[loopNum].frequency_counts[j].start_epoch = addZeroes(rawData[loopNum].frequency_counts[j].start_epoch);
data_obj.x = rawData[loopNum].frequency_counts[j].start_epoch;
data_obj.y = rawData[loopNum].frequency_counts[j].tweets;
data_array.push(data_obj);
}
var tweetArrayObj = {
"key" : "Quantity"+(i+1),
"type" : "area",
"yAxis" : 1,
"color" : tagColorArray[i],
"values" : data_array
};
tweetArrayObjsContainer.push(tweetArrayObj);
}
}
}
Your getTagQuotes function should return a promise with the $q service, check it out.
It should look like this:
function getTagQuotes(tags) {
var deferred = $q.defer();
(...)
// When the action is finished here, call resolve:
deferred.resolve();
(...)
// And in the end, return the promise
return deferred.promise;
}
And finally, in your main code, you'll do:
var promise = getTagQuotes(tags).then(function(){
console.log('promise =',promise);
// Fill chartObj with tweet data arrays
(...)
});
This function has no return statement.
function getTagQuotes(tags)