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.)
Related
I have seen answers on StackOverflow where people suggest furnishing a callback function to an AngularJS service.
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens(function callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function(callbackFn) {
$http.get('/api/tokens').then (function onFulfilled(response) {
callbackFn(response.data);
});
};
return {
getTokens: getTokens
};
});
This seems to me to be an Anti-Pattern. The $http service returns promises and having .then methods execute callback functions feels like an unhealthy inversion of control.
How does one re-factor code like this and how does one explain why the original way was not a good idea?
You should change it to
var getTokens = function() {
return $http.get('/api/tokens');
};
And, then in other module use
yourModule.getTokens()
.then(function(response) {
// handle it
});
As to why it's an anti-pattern, I'd say that, first, it doesn't allow you to further chain your success/fail handler methods. Second, it handles the control of processing the response from caller-module to called module (which might not be super-important here, but it still imposes same inversion of control). And finally, you add the concept of promises to your codebase, which might not be so easy to understand for some of the teammates, but then use promises as callbacks, so this really makes no sense.
The code can be re-factored as follows:
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens.then ( callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function() {
//return promise
return $http.get('/api/tokens').then (function onFulfilled(response) {
//return tokens
return response.data;
}
);
};
return {
getTokens: getTokens
};
});
By having the service return a promise, and using the .then method of the promise, the same functionality is achieved with the following benefits:
The promise can be saved and used for chaining.
The promise can be saved and used to avoid repeating the same $http call.
Error information is retained and can be retrieved with the .catch method.
The promise can be forwarded to other clients.
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
});
(my case applies to C#, MVC, returning JSON, got jquery, angular), but I expect it applies to more than that.
I have a website where my angular/html/js calls ~7 services through Angular controllers and async-gets/displays data (weather, road conditions, etc). Some of these take longer than others (from ms to ~10s). I'd like to have a single call to my service which returns all of this data - but doesn't wait until the last call to return anything (10s).
Is there a way to make a single call, and return results as I have them and they get displayed accordingly? Do I need to have a repeating call which has a boolean like "IsMore=T" and calls the service again? (doesn't sound efficient).
Ideally, I'd like to keep a response channel open and keeping pumping results until it's done. Possible?
I'm not sure I understand completely, but I think you could just chain the response promises together, something like:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
}
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
}
$scope.getWeatherData.then(getTrafficData).then(...chain others...);
This assumes that the service calls return a $http promise.
Anyway, whenever the promise comes in, the data will be on $scope, and hence the display will be updating as the promises arrive. It sounds like you might be using something like $q.all(), which would wait until all promises are resolved.
Buildilng on #reptilicus' answer:
$scope.getWeatherData = function () {
return myWeatherService.get().then(function (resp) {
$scope.weatherData = resp;
});
};
$scope.getTrafficData = function () {
return myTrafficService.get().then(function (resp) {
$scope.trafficData = resp;
});
};
$q.all([
$scope.getWeatherData(),
$scope.getTrafficData()
]).then(function () {
// do whatever's next, or nothing
});
... would request/receive both responses in parallel (if that's what you want). The relevant $scope property for each request will be populated when the related response is received, and the "do whatever's next" code will run once they are all complete.
Note, you need to inject $q to your controller constructor for this to work. :)
Edit: I just noticed that #reptilicus did mention $q.all. The difference between chaining the .thens and $q.all is that under chaining, one request wouldn't start until the previous was received....
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;
}
}
I'm writing an AngularJS service for a SignalR hub. Here's my factory for the service:
.factory('gameManager', [function () {
$.connection.hub.start();
var manager = $.connection.gameManager;
return manager;
}])
That code would be perfect, except that that .start() call is asynchronous, and the hub has not completed starting by the time the manager is returned. Basically, I want to block until the start is complete before returning the manager. The .start() method returns a Jquery deferred object, which I'm guessing is part of the answer, but I don't know how to use it without a callback function?
Something like the following should do the trick.
app.factory('gameManager', [function () {
return $.connection.hub.start().then(function() {
return $.connection.gameManager;
});
}])
Now your callback function will return a deferred/promise too, so the service consumer will need to be expecting that. Your consuming code might look something like this:
gameManager.then(function(gameManager) {
// do whatever with game manager
gameManager.doSomething();
});
The docs for jquery Deferred are here. In particular, check out Deferred.then().
Note that:
the deferred.then() method returns a new promise that can filter the status and values of a deferred through a function ... These filter functions can return a new value to be passed along to the promise's .done() or .fail() callbacks, or they can return another observable object (Deferred, Promise, etc) which will pass its resolved / rejected status and values to the promise's callbacks...
update:
An alternate approach (and probably the better approach - since it won't require that your consumer handle the promise) is to let the hub initialize completely, before setting up your factory, and kicking off your app controller. Something like this...
$.connection.hub.start().then(function() {
app.factory('gameManager', function() {
return $.connection.gameManager;
});
// ...
// kick off the rest of the app..
});
You will not find what you are looking for, you will have to go with Lee's answer. Javascript is mostly single-threaded and does not allow blocking (with specific exceptions, such as alert window or synchronous ajax call).