I am still learning promises in angular and have this bit of code where I am making a "GET" request two times. I want to run one get request before calling the other. This is working fine, but how would I handle errors here? If I get an error for my first GET request how do I find out what that error is and prevent my code from calling the second GET request? Examples with my code would be most helpful.
apiServices.login = function(user,password,callback) {
$http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/login/login/?username="+user+"&password="+password+"")
.then(function(contentResponse){
resultsObject.content = contentResponse;
return $http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/data/list/");
})
.then(function(dataResponse){
resultsObject.reports = dataResponse;
resultsObject.success = 1;
console.log(resultsObject);
callback(resultsObject);
apiServices.useData(resultsObject);
});
}
dummyData.login(username, password, function (dataStatus) {
if (dataStatus.success = 1) {
$rootScope.loggedIn = true;
$rootScope.selectedDashboard = 1;
} else {
console.log("Error");
}
});
I would do things slightly different from Lucas, I prefer chaining a catch block( basically it would act like the synchrounous try...catch block we use) rather than adding an error callback function so code would be like:
return $http.get(url1)
.then(function(result){
resultsObject.url1 = result;
return $http.get(url2);
}).then(function(result){
resultsObject.url2 = result;
return resultsObject;
}).catch(function(error){
// handle error.
});
P.S: most of your code is fine, but I am not really sure why you have that callback(resultsObject);, when you are using promises, callbacks are redundant, you could just return the promise chain $http.get...
You can pass a second parameter in the first callback handling. This will trigger if there's an error in the request, then you can handle it however you want:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
Or in your coding:
$http.get('/someUrl').then(successCallback, errorCallback);
More information here
Your code would look like:
$http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/login/login/?username="+user+"&password="+password+"")
.then(function(contentResponse){
resultsObject.content = contentResponse;
return $http.get("http://magainteractive.com/prototypes/cisco-ima-dashboard/cms/web/api/data/list/");
}, function(error){
//HANDLE ERROR HERE
})
.then(function(dataResponse){
resultsObject.reports = dataResponse;
resultsObject.success = 1;
console.log(resultsObject);
callback(resultsObject);
apiServices.useData(resultsObject);
});
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
There is this small function that should logically returns always, but I am getting undefined :-
function hasAccess() {
var dataObj = {
"id" : $scope.groupId
};
$http.post('http://vlinux:9099/GetItems', dataObj).then(
function(response) {
var result = response.data.result;
if(result.includes($scope.screenId)) {
return "ok";
} else {
return "nok";
}
});
}
I started getting downvotes, so wuickly adding, I debugged it and saw http call is bringing expected response and flow is jumping to the right if/else block. Problem is when I am calling this function in a variable its storing undefined.
The call is simple too :-
var allow = hasAccess();
$http.post is not synchronous, but asynchronous.
Thus, all that you have after $http.post is a promise, not the boolean you are expecting.
The documentation show wells how to provide a "callback" to your function, in case of success as well as failure :
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
// for example : manageWHenOk
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
// for example : manageWHenKO
});
Your function return a result only if the request is fine. If there is an error your function do nothing! I suggest you to add a catch function.
function hasAccess() {
var dataObj = {
"id" : $scope.groupId
};
$http.post('http://vlinux:9099/GetItems', dataObj).then(
function(response) {
var result = response.data.result;
if(result.includes($scope.screenId)) {
return "ok";
} else {
return "nok";
}
}).catch(function (error) {
// do something
});
}
If you still have undefined it means that your template is loaded before to obtain the response. In order to resolve that you can do the request in the resolve method of the $stateProvider.
React/Redux n00b here :) - working with a crappy API that doesn't correctly return error codes (returns 200 even when end point is down), therefore is messing up my Ajax calls. Owner of the API will not able to correct this soon enough, so I have to work around it for now.
I'm currently checking each success with something like this (using lodash):
success: function(data) {
// check if error is REALLY an error
if (_.isUndefined(data.error) || _.isNull(data.error)) {
if (data.id) data.sessionId = data.id;
if (data.alias) data.alias = data.alias;
resolve(data || {});
} else {
reject(data); // this is an error
}
}
I want to move this into it's own function so that I can use it with any action that performs an Ajax call, but I'm not sure where to include this.
Should this type of function map to state (hence, treat it like an action and build a reducer) or should this be something generic outside of Redux and throw in main.js for example?
You can dispatch different actions depending on if the promise was resolved or not. The simplest way would be something like this:
function onSuccess(data) {
return {
type: "FETCH_THING_SUCCESS",
thing: data
};
}
function onError(error) {
return {
type: "FETCH_THING_ERROR",
error: error
};
}
function fetchThing(dispatch, id) {
// the ajax call that returns the promise
fetcher(id)
.then(function(data){
dispatch(onSuccess(data));
})
.catch(function(error) {
dispatch(onError(error));
});
}
Heres some more documentation how to do this kind of thing...
I am trying to do multiple $http call and my code looks something like this:
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
How can I have the promise to know all $http call is successfull? If anyone of it fail, will perform some action.
You could also use $q.all() method.
So, from your code:
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
You could do:
var promises = [];
data.forEach(function(d) {
promises.push($http.get('/example.com/' + d))
});
$q.all(promises).then(function(results){
results.forEach(function(data,status,headers,config){
console.log(data,status,headers,config);
})
}),
This above basically means execute whole requests and set the behaviour when all have got completed.
On previous comment:
Using status you could get to know if any have gone wrong. Also you could set up a different config for each request if needed (maybe timeouts, for example).
If anyone of it fail, will perform some action.
From docs which are also based on A+ specs:
$q.all(successCallback, errorCallback, notifyCallback);
If you are looking to break out on the first error then you need to make your for loop synchronous like here: Angular synchronous http loop to update progress bar
var data = ["data1", "data2", "data3", "data10"];
$scope.doneLoading = false;
var promise = $q.all(null);
angular.forEach(data, function(url){
promise = promise.then(function(){
return $http.get("http://example.com/" + data[i])
.then(function (response) {
$scope.data = response.data;
})
.catch(function (response) {
$scope.error = response.status;
});
});
});
promise.then(function(){
//This is run after all of your HTTP requests are done
$scope.doneLoading = true;
});
If you want it to be asynchronous then: How to bundle Angular $http.get() calls?
app.controller("AppCtrl", function ($scope, $http, $q) {
var data = ["data1", "data2", "data3", "data10"];
$q.all([
for(var i = 0;i < data.length;i++) {
$http.get("http://example.com/" + data[i])
.then(function (response) {
$scope.data= response.data;
})
.catch(function (response) {
console.error('dataerror', response.status, response.data);
break;
})
.finally(function () {
console.log("finally finished data");
});
}
]).
then(function (results) {
/* your logic here */
});
};
This article is pretty good as well: http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/
Accepted answer is okay, but is still a bit ugly. You have an array of things you want to send.. instead of using a for loop, why not use Array.prototype.map?
var data = ["data1","data2","data3"..."data10"];
for(var i=0;i<data.length;i++){
$http.get("http://example.com/"+data[i]).success(function(data){
console.log("success");
}).error(function(){
console.log("error");
});
}
This becomes
var data = ['data1', 'data2', 'data3', ...., 'data10']
var promises = data.map(function(datum) {
return $http.get('http://example.com/' + datum)
})
var taskCompletion = $q.all(promises)
// Usually, you would want to return taskCompletion at this point,
// but for sake of example
taskCompletion.then(function(responses) {
responses.forEach(function(response) {
console.log(response)
})
})
This uses a higher order function so you don't have to use a for loop, looks a lot easier on the eyes as well. Otherwise, it behaves the same as the other examples posted, so this is a purely aesthetical change.
One word of warning on success vs error - success and error are more like callbacks and are warnings that you don't know how a promise works / aren't using it correctly. Promises then and catch will chain and return a new promise encapsulating the chain thus far, which is very beneficial. In addition, using success and error (anywhere else other than the call site of $http) is a smell, because it means you're relying explicitly on a Angular HTTP promise rather than any A+ compliant promise.
In other words, try not to use success/error - there is rarely a reason for them and they almost always indicate a code smell because they introduce side effects.
With regards to your comment:
I have did my own very simple experiment on $q.all. But it only trigger when all request is success. If one if it fail, nothing happen.
This is because the contract of all is that it either resolves if every promise was a success, or rejects if at least one was a failure.
Unfortunately, Angular's built in $q service only has all; if you want to have rejected promises not cause the resultant promise to reject, then you will need to use allSettled, which is present in most major promise libraries (like Bluebird and the original Q by kriskowal). The other alternative is to roll your own (but I would suggest Bluebird).
I have a jQuery ajax function like this:
jQuery.ajax({
url : '/blabla',
method : 'post',
data: {
bla : bla
}
}).done(function(data) {
// do lots of stuff
});
.. and I want to be able to add a check that the data passed into the done callback function doesn't have a session_timed_out value in it. Say I have many functions similar to the one above but they all do different things, but they ALL need to check if the session timed out first. Is there a proper way to extend done() so it initially checks for a timeout? I tried to do something like this but it failed:
var myAjax = function(options,callback){
var defaults = {
done: function(data){ //hijack the success handler?
if(check(data)){
callback(data);
}
}
};
jQuery.extend(options,defaults);
return jQuery.ajax(options);
}
When I use this extended function it works like before, meaning the check never gets called because it seems to be superseded by the done() callback in the actual implementation, which I guess makes sense. So I want to know if there is a way to "decorate" or extend done() function so it initially checks for the session timeout first. Or will I need to manually add this same session check to all of my ajax done's?
This snippet overrides the jQuery ajax method so you can add an extra check when it successfully returns.
(function($) {
var yourCustomCheck = function(ajaxRes) {
// Do whatever you need and return a boolean
};
var oldAjax = $.ajax;
$.ajax = function(opts) {
return $.Deferred(function() {
var _def = this;
oldAjax.call(this, opts).done(function(res) {
console.log("this is done first");
if(yourCustomCheck.call(this, res)) _def.resolve(res);
else _def.reject("timeout");
}).fail(function() {
_def.reject();
});
})
}
})(jQuery);
After this, you can use $.ajax() normally..
$.ajax({
.....
}).done(function(res) {
console.log("ok");
}).fail(function() {
console.log("no ok");
});
Here is a jsfiddle with a working example: https://jsfiddle.net/jormaechea/kffyo7qL/1/
You could chain a timeout checker:
jQuery.ajax({
url : '/blabla',
method : 'post',
data: {
bla : bla
}
}).then(timeoutCheck).then(function(data) {
// do lots of stuff
}, function(err) {
// handle error
});
function timeoutCheck(data) {
if (check(data)) {
return data;
} else {
// return a rejected promise to turn fulfilled into reject
return jQuery.Deferred.reject(new Error("timeout"));
}
}
Or, you could put this in your own ajax wrapper.
jQuery.ajaxT = function() {
return jQuery.ajax.apply(jQuery, arguments).then(timeoutCheck);
}
jQuery.ajaxT(...).then(function(results) {
// handle returned data here
// the timeoutCheck has already been done
}, function(err) {
// handle any errors here
});
Then, any ajax call you initiated with jQuery.ajaxT() would automatically have the timeoutCheck added to it's promise logic. If the ajax call succeeds and the timeout check passes, then the promise is fulfilled. If the ajax call succeeds and the timeout check fails, then the promise rejected.
I'm new to AngularJS and Breeze. I'm trying to save changes and have a problem with that. Here's my code:
In controller:
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
In datacontext:
function saveSettings() {
if (manager.hasChanges()) {
manager.saveChanges() // breaks here if there are changes
.then(saveSucceeded)
.fail(saveFailed)
.catch(saveFailed);
} else {
log("Nothing to save");
return false;
};
}
The error is thrown in angular.js and it's very unhelpful TypeError: undefined is not a function I guess there is something simple I'm missing here, but can't figure out what is it.
Want to note that it does send correct data to SaveChanges method on server, but the error is thrown before any response from the server received. After the response is received it throws another error TypeError: Cannot read property 'map' of undefined but it might be related to the fact the response I return is invalid. I haven't got to that part yet.
Can anyone anyone help with it? I'm lost here.
UPDATE
Here is how I construct my dataservice and manager:
var serviceName = "http://admin.localhost:33333/api/breeze/"; //
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
Two problems:
Your datacontext method does not return a promise so the caller cannot find anything to hang the then or fail call on.
You should be callingcatch, not fail
1. Return a promise
Your saveSettings method did not return a result in the success case so it must fail. Your method must also return a promise in the fail case ... or it will also fail.
And while I'm here, since there is no real difference between your success and fail case, you might as well move the vm.loading toggle to the finally clause.
Try this instead:
function update() {
vm.loading = true;
return datacontext.saveSettings()
.then(function () {
// .. success handling if you have any
})
.catch(function (data) {
// .. fail handling if you have any
})
.finally(funtion() {
vm.loading = false; // turn it off regardless
});
}
And now the dataContext ... notice the two return statements return a promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
} else {
log("Nothing to save");
// WHY ARE YOU FAILING WHEN THERE IS NOTHING TO SAVE?
// Breeze will handle this gracefully without your help
return breeze.Q.reject(new Error('Nothing to save'));
};
}
2. Use catch
I assume you have configured Breeze to use Angular's $q for promises (you should be using the "breeze.angular" service and have injected "breeze" somewhere).
$q does not have a fail method! The equivalent is catch. For some reason you have both attached to your query. You'll get the ReferenceError exception immediately, before the server has a chance to respond ... although it will launch the request and you will get a callback from the server too.
Try just:
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
You see many Breeze examples that call fail and fin. These are "Q.js" methods; "Q.js" is an alternative promise library - one used by Breeze/KnockOut apps and it was the basis for Angular's $q.
Both "Q.js" and $q support the now-standard catch and finally promise methods. We're slowly migrating our example code to this standard. There is a lot of old fail/finally code around in different venues. It will take time.
Sorry for the confusion.
Update savesetting function like below to return Promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges(); // return promise
} else {
log("Nothing to save");
return false;
};
}
Then you can call then and fail in update function like following.
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}