Return promise from Angular service - javascript

I have a service that (when it's all said and done) updates a value on the database.
I would like to update the view scope based on the result (success/fail) but of course the http request used by the service is asynchronous so the return value is not immediately available and comes up undefined in the controller.
If I were making the http request inside the controller, I would update the scope inside the callback function, but because Im using a service, the scope's umm... scope(?) is not available to it.
Im thinking a Promise is what I need to be returning but perhaps there is something more simple.
SERVICE
.service('doStuff',function($http){
this.update = function(data) {
$http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
})
CONTROLLLER
/* service is injected into controller etc. */
var result = doStuff.update(data);
p(result); // undefined (as expected)
I figured since Im returning from the http callback, it would wait for the result to be available before returning but I guess Im missing something.

Since $http is always async, you cannot return anything in the call back function. It is as good as not returning anything.
What you need to do is you need to return the $http promise, and then handle the callback functions in your controller.
Service:
.service('doStuff', function($http) {
this.update = function(data) {
return $http.post('http://api.internet', data);
}
})
Controller:
doStuff.update(data).then(function(result){
p(result);
});

Foremost, you need to return the query itself. Looks like
this.update = function(data) {
return $http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
Next step, you need get out of the promise function.
doStuff.update(data)
.then(function(res) {
//someone if request is success
})
.catch(function(rej) {
//someone if request is reject
});

Related

Promise in a service cancelling itself

I'm running into a bit of a problem with an Angular (1.4) service. The code is roughly as follows :
service.retrieveStuffFromServer = function() {
return httpCallFromServer().then(
function(data) {
if (Array.isArray(data)) {
return data;
}
return [];
}
);
};
I call this function in two distinct controllers. Most of the times, it works as intended, but I'm having problem in those conditions :
The HTTP call takes time to return the data
Controller A calls the service.
Controller B calls the service.
The service returns data to controller A
The call in the controller B is cancelled. The logic after it never executes
My first guess would be to slightly alter the service, to inform either of the controllers if the service is already busy so I can retry later, but I'm not sure if this is the best solution, so I'm looking for some advice.
Hard to say why it doesn't just work, but presumably something in httpCall() is preventing the same call from being made again before the 1st one completes, and it rejects if that happens. But if you want controller B call to share the response from an active previous call, you could cache the promise:
function myFunction() {
if (!myFunction.promise) {
myFunction.promise = httpCall()
.then(function(result) {
myFunction.promise = undefined;
return ...
}, function(err) {
myFunction.promise = undefined;
throw err;
});
}
return myFunction.promise;
}
This will cause the same promise from a prior call to be returned as long as the prior call is still unresolved.
Using a property of the function itself as a cache is a convenient way to keep state associated logically with the function itself. You could just use any variable defined outside the scope of myFunction though.

Angular Service Promises

So, I've got an Angular app that makes restful calls to the server. There is a service that wraps up the calls to the server. I currently have a method on the service that simply returns the promise from the $http service. I'd like to add some additional processing on that method call, but I'm not sure how to do it because of the asynchronous nature of the promise.
Currently in typescript:
class BoardService {
private $http;
constructor($rootScope: IRootScope, $http: ng.IHttpService) {
this.$http = $http;
}
fetchBoard(id: number) {
return this.$http.get("/api/board/" + id);
}
}
I'd like to get it to something like this:
fetchBoard2(id: number) {
this.$http.get("/api/board/" + id).success(function(data)
{
// Manipulate the data
});
// return manipulated data;
}
How would you do this?
Tricky sentence warning! Because promises are asynchronous, anything returning data based on data from a promise must itself return a promise. You want fetchBoard2 to return a promise that gets resolved once the $http promise has come back and you've manipulated the data. You do this with Angular's $q service.
fetchBoard2(id: number) {
var deferred = $q.defer();
$http.get("/api/board/" + id).success(function(data) {
var newData = doSomething(data);
deferred.resolve(newData);
});
return deferred.promise;
}
Managing extra deferred objects gets quickly fiddly, so you can use then to insert your own manipulation into the pipeline.
fetchBoard3(id: number) {
return $http.get(...).then(function(data) {
return doSomething(data);
});
}
For more detail, here's a good article.
The $http module only exposes the asynchronous version of XMLHttpRequest so the signature you're looking for is impossible. Unless want to fallback to another framework (e.g. jQuery), you'll have to use the Promise object returned.
Think of it as a factory object with which you register handlers that will be called when the data comes back. They can be chained, so if you want to process the data before handing it downstream, you can simply do that in the handler you register with the then method. Any result you return in the handler will become the data to the next then handler.
(Please note that unlike success(), the argument to your handler is the IHttpPromiseCallbackArg type not the data itself.)

Why does angular $resource add extra objects ($promise, $resolve...) to my data response?

I return a resource with a URL
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
function(error){ $scope.msg = "error" } );
Resource returns
["item1"...."item_n",.....,"$promise", "$resolved", "$get", "$save", "$query", "$remove", "$delete"]
Why do I get all those objects in my data set. I'm guessing $promise just returns all this and waits for the server response. But once I have the server response where can I just get my server data without the Promise jargon?
If you look at the angular source here:
https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L505
There is a toJSON method on the Resource prototype chain that will accomplish this for you.
For example:
$resource("http://foo.com/bar.json").get(function(res) {
$scope.result = res.toJSON();
});
You need to return wrapped result like {'result': { 'some_key': 'some_val' }} from your backend.
Or just do like described above.
Diary.getSharedWithMe(function(data) {
delete data.$promise;
delete data.$resolved;
_self.sharedDiariesWithMe = data;
}, function(error) {
console.log(error)
});
$resource returns an object or array that will have your data when the call completes. All those functions are there to help you out and $resource is mainly intended for CRUD operations. If you want the data, you have to wait for it to get returned so you might as well use the promise. If you want to strip all of those properties you can use angular.toJson to convert it to json, but angular does that for you when posting it back to a resource or $http call so you shouldn't have to.
$scope.data = $resource("http://foo.com/bar.json").get();
// $scope.data does not have your data yet, it will be
// populated with your data when the AJAX call completes
...
// later in a call from a save button maybe you can just do
// this to post your changes back:
$scope.data.$save();
So in case someone else is stumbling here and didn't understand promises/angularjs here is what is going on. When you use .then() or .get() you get a promise and some helper functions all in the same object. This is awesome because then you don't worry about callbacks being defined and whether data is available because the promise object always has some properties. This object contains your raw data in another object within. So the promise object is nested, you just have to reference the data object within when the data is ready.
Here's what I was doing
$resource("http://foo.com/bar.json").get().
$promise.then(function(data){ $scope.result = data},
//data is actually a promise object.
function(error){ $scope.msg = "error" } );
promise object
Note the data is actually under another object called "data". So in your success callback to get just the data you should do in this case: data.data
To automatically remove them from every request, you can add an interceptor:
angular.module('app').config(config);
config.$inject = ['$httpProvider'];
function config($httpProvider) {
$httpProvider.interceptors.push(interceptor);
}
interceptor.$inject = [];
function interceptor() {
return {
request: (config) => {
if (config.data) {
delete config.data.$promise;
delete config.data.$resolved;
}
return config;
}
};
}

Do Angular JS $http promises to behave like true $q promises?

I'm aware that Angular can handle promises from within controllers. For example:
function MyCtrl($scope) {
$scope.myvar = getDeferredPromise();
}
The main angular digest loop handles this gracefully, assigning whatever value the deferred function finally returns later to myvar.
However, although the $http.get() method returns a promise, I cannot get it to work in this way. For example:
function MyCtrl($scope, $http) {
$scope.myvar = $http.get('/url');
}
The 'promise' the get method returns has a success method which takes a function that is assigned the data that one would wish to assign to myvar.
However, it also has a then method - but that is given the entire response object - not just that data part! This is what seems to end up getting assigned to myvar!
This fiddle may help: http://jsfiddle.net/QKnNC/1/
Am I doing something wrong here? Or is this somehow 'by design'?
ng.$http
The $http service is a function which takes a single argument — a
configuration object — that is used to generate an HTTP request and
returns a promise with two $http specific methods: success and error.
$http returns a promise, so you need to chain then to get the data.
IPService.getV1().then(function (response) {
console.log(response)
$scope.value1 = response.data;
});
then is the general promise function, that takes a success and error callback and you get the resolved value, whatever it may be. success and error are $http specific, and are aliases for then with one exception: they set a bunch of useful arguments rather than just the data. See the source.
It is by design. Your getV2() method is what you want. Since you are using GET, you could save the result of your promise and return that on subsequent calls to getV2():
var v2promise, v2data;
return {
getV2: function() {
if(!v2promise) {
v2promise = $http.get('http://ip.jsontest.com/').then(
function(response) {
v2data = response.data;
return v2data;
});
}
return v2promise;
}
}

don't want to expose $http in the controller

There is related question about processing $http in a service, but I wanted to elaborate slightly. I would like my controller to be able to execute service calls using API similar to the Angular $http API:
$scope.login = function(user) {
securityService.login(user).success(function(data) {
$scope.data = data;
}).error(function(data) {
$scope.error = data;
});
};
That's a nice readable API. On the surface, all I would need to do in the service API is:
return {
name : 'User Service',
login : function(user) {
return $http.post("/api/login", user);
}
};
Great, it returns the promise and the success and error messages come with. But... What if I want to deal with the success and failure cases in the service? I want to maintain the nice, readable service API. In this case, maybe I'd want to preserve the user so that I could expose methods like securityService.currentUser() or `securityService.isLoggedIn()'.
I tried the $http.post().then(...) promise API, but those return the entire HTTP response. Again, I want to isolate HTTP to the services and maintain a similar concrete callback API.
You could make your own promise in login using angular's $q:
var deferred = $q.defer(),
promise = deferred.promise;
$http.post("/api/login", user).success(...).error(...)
return promise;
Within the success/failure of the $http promise in your service, resolve/reject your deferred object.
Edit:
Expanding upon the $http.post:
$http.post("/api/login", user).success(function(data) {
if (data == "foo") { // success case?
deferred.resolve('you logged in!');
} else {
deferred.reject('something really bad happened!');
}
})
I just ran a test case and it seems that if I return the promise from the service API like this:
return $http.post("/api/login", user).success(...).error(...)
I can also do the same in the controller:
service.login(user).success(...).error(...)
and both get called, the service first and the controller second. That's perfect!

Categories