Is this a bad way to do this? I'm basically chaining promises, where each successful return from server, launches a new http.get() for more information BUT NOT when it errors. No more http.get()s if it causes an errorCallback!
$http.get(...).then(function() {
$http.get(...).then(function(){
$http.get(...).then(function(){}, function(){}),
function(){}); },
mainErrorCallback);
Would it make a difference if it was instead of "$http.get()" it does "ViewsService.loadViews()" inside the
$http.get(...).then( function() { ViewsService.loadViews(); }, function(){ console.log("error"); }).
EDIT: Here's what I mean, synchronously.. it seems like it works, but code needs cleanup/efficency to look a little neater:
http://jsfiddle.net/4n9fao9q/6/
(with delayed http requests): http://jsfiddle.net/4n9fao9q/26
$http.get(...).then((res) => {
//res has data from first http
return $http.get(...);
}).then((res) => {
//res has data from second http
return $http.get(...);
}).then((res) => {
//res has data from third http
}).catch((err) => {
//exploded
});
I think is cleaner. You can replace $http.get with whatever function returns a promise. If ViewsService.loadViews() returns a promise, you can use it.
As asked in the comments.
...
ViewsService.loadViews = function() {
//returns a promise
return $http.get(...);
}
OR
ViewsService.loadViews = function() {
return new Promise((resolve, reject) => {
$http.get(...).then((result) => {
//whatever
return resolve();
})
})
return $http.get(...);
}
With any of this options for loadViews you can do ViewsService.loadViers.then(etc)
Is this a bad way to do this?
Efficiency
Unless you are using the response from the first request as input to the following request then this isn't
a very efficient way to do this, as each request will be blocked until the previous one has returned. A better
way would be to use $.all($http.get(...),$http.get(...))
Style
The nested calls (the pyramid of doom) are difficult to read. As each call has the same failure response you could just chain these calls instead. e.g.
$http.get(..).then
($http.get(..)).then(
($http.get(..)).catch(errHander)
Related
I am using $http to make a call. Based on the successful result of the call I may decided to throw an error/reject and have it trickle down to the next call as an error. However if an error is thrown it just halt the process. How can I force the $http promise to reject without wrapping it in some $q code?
// A service
angular.module('app').factory('aService', function ($http, config) {
return {
subscribe: function (params) {
return $http({
url: '...'
method: 'JSONP'
}).then(function (res) {
// This is a successful http call but may be a failure as far as I am concerned so I want the calling code to treat it so.
if (res.data.result === 'error') throw new Error('Big Errror')
}, function (err) {
return err
})
}
}
})
// Controller
aService.subscribe({
'email': '...'
}).then(function (result) {
}, function (result) {
// I want this to be the Big Error message. How do I get here from the success call above?
})
In the above code I would like the Big Error message to end up as a rejected call. However in this case it just dies with the error. This is how I handle things in say Bluebird but it's a no go here.
Ti continue the Chain in a rejected state just return a rejected promise $q.reject('reason') from your $http result something like
$http.get(url).then(
function (response){
if(something){
return $q.reject('reason');
}
return response;
}
)
That way you'll get a a rejected promise and can react to it even when the api call is successful.
I am trying to use Breeze to get data from a server into an AngularJS app, and although the server is sending the JSON data, the client app is not getting it. The closest I've gotten to identifying the issue using the debugger is to see that the following function getRemoteEntities(), which is part of a Factory, should return a promise but instead returns an empty Object {} when called with a valid entityURL and jsonAdapter:
[...]
var manager = entityManagerFactory.newManager();
[...]
return {
getRemoteEntities: function (entityUrl, jsonAdapter) {
var query = breeze.EntityQuery
.from(entityUrl)
.using(jsonAdapter);
return manager.executeQuery(query)
.then(function (results) {
return results;
})
.catch(function (error) {
return $q.reject(error);
});
}
}
I have checked, and the code does use the Breeze Angular Service as described here. I do not understand what is not working.
EDIT: Removing the .using(jsonAdapter) means that I am able to get and resolve the promise, suggesting that it might be doing something that messes it up. Here is an example (they all follow this pattern):
.value('jsonProfileResultsAdapter', new breeze.JsonResultsAdapter({
name: "xyz", // mild obfuscation
extractResults: function (data) {
var results = data.results;
if (!results) throw new Error("Unable to resolve 'results' property");
return results;
},
visitNode: function (node, parseContext, nodeContext) {
if (node) {
if (node.person && node.assignments) {
return {entityType: "EmployeeModel"}
}
}
}
}))
What is the jsonAdapter doing? That's an unusual feature (not wrong, just unusual). Maybe you're doing something inside it that blows up the promise.
Take it away and see what you get. If you get a promise, even a failed promise, then you're on to something.
I have following code in Ionic framework,
var stopScan = false;
$scope.StopScan = function() {
stopScan = true;
};
$scope.ScanContacts = function() {
Contacts.unchecked().then(function(contacts) {
var promise = $q.all(null);
angular.forEach(contacts, function(contact) {
promise = promise.then(function() {
return $http.post(apiEndpoint+'/check', {number: contact.number})
.success(function(res) {
Contacts.update(contact.id, res);
if(stopScan)
// do break loop;
})
.error(function(err) {
console.log(err);
});
});
});
});
};
It's do sending http request in loop synchronously, and break on $http error, exactly like I wanted. But how I do break the loop in the $http success? I've tried throw 'Scan stopped'; and $q.reject('Scan stopped'); but no success.
First of all, angular.forEach does not support breaking (see here and here)
Second, break statement must be directly nested within the loop, even if it was a for or while loop.
And lastly, .success is happening asynchronously, after the loop has executed, so breaking there via some other mean would have been meaningless anyway.
It seems like you expect stopScan to be set asynchronously elsewhere (for example, in response to a click from the user), but you have to decide exactly what it means to stop - does it mean "do not make any more $http.post requests", or does it mean "make all the requests, but don't not process the response?". (Your example seems to imply the latter, because you're attempting to handle it in .success, but you should know, though, that POST typically implies that changes were made on the server).
You have to understand that once you kick off an HTTP request, it's going out (or it's pending, subject to max number of connections, which is browser-dependent).
So, what you could do is fire all of the requests at once and in parallel, and then manually "timeout" ($http supports a promise-based timeout) the ones that haven't been completed:
var stopScanTimeout = $q(function(resolve){
$scope.stopScan = function(){
resolve();
}
})
var promises = [];
angular.forEach(contacts, function(contact) {
var httpPromise = $http({ method: "POST",
url: apiEndpoint+'/check',
data: {number: contact.number},
timeout: stopScanTimeout })
.then(function(response){ return response.data; },
function(error) { return {error: error};});
promises.push(httpPromise);
});
Then you could handle all the results together, and some would be "errors" (but "soft" errors) if they were not completed in time:
$q.all(promises).then(function(results){
for (var i = 0; i < results.length, i++){
var result = results[i];
if (result.error) continue;
// otherwise, process the result
Contacts.update(contact.id, result);
}
})
If you want to run with parallel HTTP requests, then go with #NewDev's answer.
However if you want to stick with serial requests, then "breaking out of the loop" couldn't be simpler.
All you need to do is throw, which won't break as such but will send the constructed promise chain down its error path. At the stop point, there will be no unreturned requests and no more requests will be sent.
I would write something like this, using contacts.reduce(...) to build the chain.
$scope.ScanContacts = function() {
return Contacts.unchecked().then(function(contacts) {
return contacts.reduce(function (p, contact) {
return p.then(function() {
return $http.post(apiEndpoint + '/check', { number: contact.number })
.then(function(res) {
if(stopScan) throw new Error('scan stopped');
Contacts.update(contact.id, res);//you can choose to service the last response or not but placing this line above or below the throw line.
}, function(err) {
// As the second .then param, this callback will catch any http errors but not the 'scan stopped' error.
// By catching http errors, the scan will be allows to continue.
// To stop on http error, either remove this callback or rethrow the error.
console.log(err);
});
});
}, $q.when());
});
};
Here's evidence that throwing will give the required "stop" effect.
If throwing doesn't work in the real code, then it would seem that something else is wrong.
I'm pretty new to AngularJS and am learning as I go along. How do I chain successive $http posts? I need reponse data from the first $http POST to use in the second $http POST, of which I'll also need the response which this second POST returns.
$http({
method: 'POST',
url: 'http://yoururl.com/api',
data: '{"field_1": "foo", "field_2": "bar"}',
headers: {'Content-Type': 'application/json'}
}).then(function(resp) {
$scope.data_needed = resp.data_needed;
// Can't possibly do another $http post here using the data I need, AND get its reponse?
// Would lead to a nested relationship, instead of adjacent chaining.
}, function(err) {
// Handle error here.
});
I've found out that it isn't an option to chain another $http post to the last line of code with another .then(function(resp) {});, for the same reason (referring to 1st comment in code block above).
Any advice? All I can seem to find are examples of chaining $http GETs, which do not involve getting and using a response. Cheers.
This is the way to go:
$http({...})
.then(
function success1(response) {
var data = response.data;
$scope.xxx = data.xxx;
return $http({...});
},
function error1(response) {
return $q.reject(response);
}
)
.then(
function success2(response) {
var data = response.data;
$scope.yyy = data.yyy;
},
function error2(response) {
// handle error
}
);
When a then() function returns a promise (the return $http(...) part), the chained then() gets called with the resolved value of the second promise. Also note the return $q.reject(...) part, necessary for the flow to proceed to the second error function, instead of the second success function.
I would like to make multiple Ajax calls in a chain. But I also would like to massage the data after each call before making the next call. In the end, when All calls are successful, I would like to run some other code.
I am using Angular $http service for my Ajax calls and would like to stick to that.
Is it possible?
Yes, this is handled very elegantly by AngularJS since its $http service is built around the PromiseAPI. Basically, calls to $http methods return a promise and you can chain promises very easily by using the then method. Here is an example:
$http.get('http://host.com/first')
.then(function(result){
//post-process results and return
return myPostProcess1(result.data);
})
.then(function(resultOfPostProcessing){
return $http.get('http://host.com/second');
})
.then(function(result){
//post-process results of the second call and return
return myPostProcess2(result.data);
})
.then(function(result){
//do something where the last call finished
});
You could also combine post-processing and next $http function as well, it all depends on who is interested in the results.
$http.get('http://host.com/first')
.then(function(result){
//post-process results and return promise from the next call
myPostProcess1(result.data);
return $http.get('http://host.com/second');
})
.then(function(secondCallResult){
//do something where the second (and the last) call finished
});
The accepted answer is good, but it doesn't explain the catch and finally methods which really put the icing on the cake. This great article on promises set me straight. Here's some sample code based on that article:
$scope.spinner.start();
$http.get('/whatever/123456')
.then(function(response) {
$scope.object1 = response.data;
return $http.get('/something_else/?' + $scope.object1.property1);
})
.then(function(response) {
$scope.object2 = response.data;
if ($scope.object2.property88 == "a bad value")
throw "Oh no! Something failed!";
return $http.get('/a_third_thing/654321');
})
.then(function(response) {
$scope.object3 = response.data;
})
.catch(function(error) {
// this catches errors from the $http calls as well as from the explicit throw
console.log("An error occured: " + error);
})
.finally(function() {
$scope.spinner.stop();
});