AngularJS : promises, can you pass a promise back after using .then()? - javascript

I'm still new to Angular and promises so I hope I have the correct idea here.
I currently have a data layer service which uses restangular to get some data, then returns a promise, like this...
dataStore.getUsers = function (params) {
return users.getList(params);
};
Then, my controller which has called this function receives a promise back, like this...
$dataStore.getUsers(params).then(function (response) {
$scope.users = response;
}, function(response) {
$log.error("Get users returned an error: ", response);
});
This is working well, but I'd like to use the promise inside of my datastore before passing it back. I'd like to use the .then() method to check if it failed and do some logging, then, from the sucess function and from the failure function I'd like to return the original promise back to my controller.
My controller would then be able to use the .then() method like it already is, in fact, I don't want my controller code to change at all, just my datastore code.
Here's some semi-pseudo code to show what I'd like my datastore function to do...
dataStore.getUsers = function (params) {
users.getList(params).then(function (response) {
$log("server responded")
return original promise;
}, function(response) {
$log.error("server did not respond");
return original promise;
});
};

You were actually not far off at all in your pseudo code. Promises chain:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}, function(failure) {
$log.error("server did not respond");
// change to throw if you want Angular lever logs
return $q.reject(failure);
});
};
The controller now gets resolved/rejected with the same value. The log requires tapping into the promise so you must add a .then handler to deal with it. Other promise libraries have convinicene methods for this but $q is minimalistic in this regard.
Alternatively, you can use nicer catch syntax, as well as propagate the errors to your logs:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}).catch(function(failure) {
$log.error("server did not respond");
throw failure;
});
};

Related

How to delay a $httprequest?

I am trying to to delay my $http call like this:
githubService.getUserEvents = function getUserEvents() {
return $timeout(
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
}),
5000);
};
When I run this it does not seem to perform the delayed request and I cannot see any errors in the chromeconsole? See also here jsbin
How can I call a delayed $http request without using an interceptor?
jsbin
Deleted answer had this right I think, you do need a deferred since you can't return from the $timeout, also as comment indicated needed to inject $timeout service.
githubService.getUserEvents = function getUserEvents() {
var deferred = $q.defer();
$timeout(
function(){
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
deferred.resolve(_.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
}));
})
},2000);
return deferred.promise;
};
Looks like I went over the public API limit here but should work.
EDIT
Per comments tried with just returning the $timeout promise and it does seem to work here (didn't seem to work when I tried that at first yesterday even after fixing the $timeout, so not sure what was wrong but here's the function working without making an extra deferred object)
githubService.getUserEvents = function getUserEvents() {
return $timeout(function(){
return $http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
})
},2000);
};
Still I have to stand by my point in the comments. So long as you are aware making your own defer is going to have some overhead and you are resolving whatever without going through the whole promise chain then I don't see an issue with doing it the first way (and I find it easier to understand). However just for clarification the $timeout promise does resolve with the value returned from the function it triggers (this makes sense but the docs made it sound as though $timeout resolved the moment the function was triggered not when it was complete... didn't get to the return part of the docs though tbh).

angularjs - Trouble returning data from a factory

I'm trying to create a db factory that returns data from the database to the client after the data is successfuly posted but it returns as 'undefined' for some reason.
My factory function looks like this:
uno.factory('adbFactory', ['$http', function($http){
var fact = {};
fact.get = function(http, query, isAll) {
//var query = "get all blog_posts";
http.post('php/adb/adb.php', {'query': query, 'all': isAll})
.success(function(data){
//console.log(data);
return data;
})
.error(function(){
console.log('Error...');
});
};
return fact;
}]);
And my controller resembles this:
uno.controller('newsCtrl', function($scope, $http, adbFactory){
$scope.derp = 'derp!!!!!';
console.log(adbFactory.get($http, 'get users 1', false));
});
don't worry about the 'get users 1 etc etc' string, i created a function in php that renders a SQL query based on given parameters. Is there something in my factory code i need to improve on??
I would advice returning the promise from the factory and handling the success and error events in the controller instead.
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory.get($http, 'get users 1', false).success(function(data) {
console.log(data);
});
});
fact.get method has no return statement, that's why it returns undefined.
Also, this callback is useless because it is called asynchronously
.success(function(data){
//console.log(data);
return data;
})
I think you want somethig like:
fact.get = function(http, query, isAll) {
return http.post('php/adb/adb.php', {'query': query, 'all': isAll});
};
uno.controller('newsCtrl', function($scope, $http, adbFactory){
adbFactory
.get($http, 'get users 1', false)
.success(function(data){
console.log(data);
})
.error(function(){
console.log('Error...');
});
});
You have to keep in mind that you are performing some asynchronous request.
You have two way to retrieve your data :
Following the callback way
Following the promise way
As you know, $http service return promise, and has some callback method, like .success() and .then() for example.
For promise, $q.defer() is a promise manager from the deferred API.
$q.defer() get 2 methods :
resolve(value) : which resolve our associated promise, by giving her the final value
reject(reason) : which resolve an promise error.
So you can do :
Service
(function(){
function Service($http, $q){
var defer = $q.defer();
//Callback way
function get(callback){
$http.get('app.php').success(function(data){
//Pass our data to the callback
callback(data);
});
}
//Promise ways
function getPromise(){
$http.get('app.php').success(function(data){
//Resolve the data
defer.resolve(data);
});
//Return our promise
return defer.promise;
}
return {
get: get,
getPromise: getPromise
};
}
angular
.module('app')
.factory('Service', Service);
})();
Controller
(function(){
function Controller($scope, Service) {
//Our callback method
function print(data){
console.log(data);
}
//Retrieve our data by using callback way
Service.get(print);
//Retrieve our data by using promise way
var promise = Service.getPromise();
//When promise is resolved
promise.then(function(data){
//Retrieve our data
console.log(data);
});
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
But what should i use ? I think that use promise is better than callback, because you can handle easily your request. Moreover, you can perform promise chaining, and avoid the famous callback hell.

Angular custom method on $resource with .then() function causing error

I have this in my Angular service:
return $resource(BASE + '/cases/:id',
{id: '#id'}, {
status: {method: 'GET', params: {status: '#status'}}
});
When using the method added to the $resource definition along with the promise's .then() function, I'm getting an error:
Cases.status({status: 'pending'})
.then(function(res) {
console.log(res);
$scope.cases.pending = res.data.cases;
})
.then(function() {
$scope.tabbed.pending = true;
});
After the above snippet is run, the error I get is:
TypeError: undefined is not a function on this line: .then(function(res) {
Can I not use these functions as I usually do when I'm using an extra method defined on the $resource?
I think you need to use $promise of $resource object which will call success function when actual promise gets resolved & then you could proceed with the promise chain.
CODE
Cases.status({status: 'pending'})
.$promise
.then(function(res) {
console.log(res);
$scope.cases.pending = res.data.cases;
})
.then(function(cases) {
$scope.tabbed.pending = true;
});
You have to use $promise in order to access the promise is created on that call, like this:
Cases.get({id: 123}).$promise
.then(function (success) {
//do something
}).then(function (error) {
//do something else
})
Or you can send both functions as callbacks:
Cases.get({id: 123}, function (success) {
//do something
}, function (error) {
//do something else
});
Tip
You don't have to add a new method to that $resource to send a GET to the same url, you can just have your $resource be plain:
//... code
return $resource(BASE + '/cases'});
and when you pass the parameters to it (if you are passing them as in the example) it will match the keys according to the object so you can just say:
Cases.get({status: 'something'}).$promise
.then(function(success){
//... code
})
.then(function(err){
//... code
});
Cases.get({id: 123}).$promise
.then(function(success){
//... code
})
.then(function(err){
//... code
});

ui-router, resolve, $http not working as documented

I have bug (or maybe wrong usage?) with ui-router, resolve, factory and $http.get call.
Here's a snippet of the code in the config section:
$stateProvider
.state('index', {
url: '/',
views: {
'': {
templateUrl: './views/layout.html',
controller: 'MyAppCtrl'
},
'app-navbar#index': {
templateUrl: './views/app-navbar.html'
},
'app-accordion#index': {
templateUrl: './views/app-accordion.html',
controller: 'AppController',
resolve: {
appPromiseObj: function (AppFactory) {
return AppFactory.getApps();
}
}
},
...
and have the following AppFactory
myApp.factory('AppFactory', function ($http) {
var appFac = {
apps: []
};
appFac.getApps = function () {
promiseObj = $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
});
return promiseObj;
};
return appFac;
});
But when I run the app, the console.log message in the 'success' callback never gets executed. The browser console log shows the http call executes OK with code 200. I am assuming this means angular thinks it has failed or should I be doing something else?
I even tried returning the $q promise object (as suggested in other somewhat related stack overflow threads) but no success. In the factory code if I use test data (i.e., no HTTP call) everything works fine even if I don't return a promise object. Any pointer on where the problem could be? Appreciate any pointers to help me debug...
I created working plunker here. The problem was incorrect promise handling inside of the AppFactory.getApps(). We need to return the promise at the begining and then also return some adjusted stuff on success. Now it works...
This is the main change I made:
// INSTEAD of this
// appFac.getApps1 = function () {
// promiseObj = $http.get('http://localhost:4567/applications')
// .success(function (data) {
// console.log("success calling http");
// angular.copy(data, appFac.apps);
// });
//
// return promiseObj;
// Let's use this
appFac.getApps = function () {
return $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
return appFac.apps
});
// this is already returned above
//return promiseObj;
Check it in action here
EXTEND
Based on your extended plunker (still not fully working as expected)
-http://plnkr.co/edit/c89j3eFvYyguMt0QznAI?p=preview
I created adjsuted and workin version
http://plnkr.co/edit/f2aucPcbtzqwIEogbjuJ?p=preview
The only changes was proper naming (e.g. app.js to be loaded as a script instead of script.js...). But at the end, the promise is now resolved and this json:
[{"id":10566982,"networkID":34256899,"appID":56114114
,"name":"10566982name","description"
...
]
Is loaded and converted into accordion:
56114114name
58616695name
Finally to answer your question in the comment below:
but what is the difference between promiseObj = $http(...)... ; return promiseObj and return $http (...); ?
There is no difference (except I see my approach a bit more clear). The real difference is:
angular.copy(data, appFac.apps);
vs
return appFac.apps
as a final statement of the .success() method. It MUST return something. tha's the trick

Can I act on and then forward the results of a AngularJS $http call without using $q?

I have functions like the getData function below.
I understand that $http returns a promise. In my current set up I am using $q so that I can do some processing of the results and then return another promise:
var getData = function (controller) {
var defer = $q.defer();
$http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
defer.resolve({
data: data,
dataPlus: dataPlus
});
})
.error(function (error) {
defer.reject({
data: error
});
});
return defer.promise;
}
Is there any way that I can do this without needing to use the AngularJS $q (or any other $q implementation) or is the code above the only way to do this? Note that I am not looking for a solution where I pass in an onSuccess and an onError to the getData as parameters.
Thanks
As you say $http.get already returns a promise. One of the best things about promises is that they compose nicely. Adding more success, then, or done simply runs them sequentially.
var getData = function (controller) {
return $http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
return {
data: data,
dataPlus: dataPlus
};
})
.error(function (error) {
return {
data: error
};
});
}
This means that using getData(controller).then(function (obj) { console.log(obj) });, will print the object returned by your success handler.
If you want you can keep composing it, adding more functionality. Lets say you want to always log results and errors.
var loggingGetData = getData(controller).then(function (obj) {
console.log(obj);
return obj;
}, function (err) {
console.log(err);
return err;
});
You can then use your logging getData like so:
loggingGetData(controller).then(function (obj) {
var data = obj.data;
var dataPlus = obj.dataPlus;
// do stuff with the results from the http request
});
If the $http request resolves, the result will first go through your initial success handler, and then through the logging one, finally ending up in the final function here.
If it does not resolve, it will go through the initial error handler to the error handler defined by loggingGetData and print to console. You could keep adding promises this way and build really advanced stuff.
You can try:
Using an interceptor which provides the response method. However I don't like it, as it moves the code handling the response to another place, making it harder to understand and debug the code.
Using $q would be the best in that case IMO.
Another (better ?) option is locally augmented transformResponse transformer for the $http.get() call, and just return the $http promise.

Categories