How do you handle errors from chained promises? - javascript

I am building an Angular app, and have a factory that executes an XHR, and returns the promise to the controller that called it. When I get an error, it gets caught in the factory fail method but because I'm returning it as a promise, it still calls the success method in the controller. Is there a way that I can get the factory error to pass on to the controller fail method and prevent the controller success method from being called? Reason being is that I'd like to properly display success and error notifications on the client side. Am I just going about this entirely wrong?
Example factory method:
function add(payload) {
return $http.post('/companies', payload)
.then(success)
.catch(fail);
function success(response) {
return response.data;
}
function fail(error) {
$log.log('Company Factory XHR failed: ', error.data);
}
}
Corresponding controller method:
function add(isValid) {
if (isValid) {
var payload = {
company: vm.new_company
};
companyFactory.add(payload)
.then(success)
.catch(fail);
}
function success() {
getCompanies();
vm.new_company = {};
toastrFactory.success("Added company!");
}
function fail(err) {
$log.log('Companies Controller XHR Failed: ' + err.data);
}
}

You don't need to handle the promise in the factory if you are handling it in the controller.
In the factory you want to just
return $http.post('/companies', payload);
Then handle it like you are already in your controller and you're good to go.
Using .then and .catch are functions on a promise, the way you are doing it now in your factory, you aren't returning a promise, you are returning other things after the promise is resolved. You just want to return the promise alone, which is the $http.post() function.
Edit:
To add a little more depth, this is how I setup my factories for this kind of stuff...
function factory() {
var self = {};
self.addCompany = function(payload) {
return $http.post('/companies', payload);
}
return self;
}
Then handle it in controllers like
factory.addCompany(payload)
.then(function(data) {
console.log('success', data);
})
.catch(function(data) {
console.log('something happened', data);
})

Related

Angular 1.6.4 $http post callback not called [duplicate]

I'm finding it hard to understand the "deferred antipattern". I think I understand it in principal but I haven't seen a super simple example of what a service, with a differed promise and one with antipattern, so I figured I'd try and make my own but seeing as how I'm not super in the know about it I'd get some clarification first.
I have the below in a factory (SomeFactory):
//url = 'data.json';
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
deferred.resolve(response.data);
} else {
return deferred.reject(response.data);
}
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
The reason I am checking its an object is just to add a simple layer of validation onto the $http.get()
And below, in my directive:
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
})
.catch(function(response) {
//Do error handling here
});
Now to my uderstanding, this is an antipattern. Because the original deferred promise catches the error and simply swallows it. It doesn't return the error so when this "getData" method is called I have do another catch to grab the error.
If this is NOT an antipattern, then can someone explain why both require a "callback" of sorts? When I first started writing this factory/directive I anticipated having to do a deffered promise somewhere, but I didn't anticipate having to .catch() on both sides (aka I was sort of thinking I could get the factory to return the response or the error if I did a SomeFactory.getData()
Is this a “Deferred Antipattern”?
Yes, it is. 'Deferred anti-pattern' happens when a new redundant deferred object is created to be resolved from inside a promise chain. In your case you are using $q to return a promise for something that implicitly returns a promise. You already have a Promise object($http service itself returns a promise), so you just need to return it!
Here's the super simple example of what a service, with a deferred promise and one with antipattern look like,
This is anti-pattern
app.factory("SomeFactory",['$http','$q']){
return {
getData: function(){
var deferred = $q.defer();
$http.get(destinationFactory.url)
.then(function (response) {
deferred.resolve(response.data);
})
.catch(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
}])
This is what you should do
app.factory("SomeFactory",['$http']){
return {
getData: function(){
//$http itself returns a promise
return $http.get(destinationFactory.url);
}
}
while both of them are consumed in the same way.
this.var = SomeFactory.getData()
.then(function(response) {
//some variable = response;
},function(response) {
//Do error handling here
});
There's nothing wrong with either examples(atleast syntactically)..but first one is redundant..and not needed!
Hope it helps :)
I would say that it is the classic deferred anti-pattern because you are creating needless deferred objects. However, you are adding some value to the chain (with your validation). Typically, IMO, the anti-pattern is particularly bad when deferred objects are created for very little or no benefit.
So, the code could be much simpler.
$q promises have a little documented feature of automatically wrapping anything returned inside a promise in a promise (using $q.when). In most cases this means that you shouldn't have to manually create a deferred:
var deferred = $q.defer();
However, that is how the documentation demonstrates how to use promises with $q.
So, you can change your code to this:
return {
getData: function(){
return $http.get(destinationFactory.url)
.then(function (response) {
if (typeof response.data === 'object') {
return response.data;
} else {
throw new Error('Error message here');
}
});
// no need to catch and just re-throw
});
}
Using the $q constructor is a deferred anti-pattern
ANTI-PATTERN
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
return $q(function(resolve, reject) {
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
$http(req).then(function(response) {
resolve(response.data);
}, function(error) {
reject(error);
});
});
}
CORRECT
vm.download = function() {
var url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
var req = {
method: 'POST',
url: url,
responseType: 'arraybuffer'
};
return $http(req).then(function(response) {
return response.data;
});
}
The $http service already returns a promise. Using the $q constructor is unnecessary and error prone.

Angular httpPromise

I have a request function :
function search(request) {
return $http.post('/path/to/resource', request);
}
I can call it like this :
search({/*...*/})
.success(function() {})
.error(function() {})
As I often need to find some objects by their ID, I need a shortcut function. I cannot find how to create this function so that I can also chain it with success() and error() functions.
I searched how to create a promise in angular and found the documentation about $q and this is what I tried :
function searchById(id) {
var deferred = $q.defer();
search({id: id}).
then(function (response) {
deferred.resolve(response.data.results[0]);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
}
I can only call it like this :
searchById().then(successCallback, errorCallback);
I would like to be able to call it like this :
searchById()
.success(successCallback)
.error(errorCallback);
The documentation about $q indicates that it returns a promise whereas the documentation about $http indicates that it returns an httpPromise but I cannot figure how to create an httpPromise.
Any idea?
In angular promises the error callback should be catch not error, try this
searchById()
.then(successCallback)
.catch(errorCallback);
sjokkogutten is correct that you don't need to use $q in this case you can simplify this
var deferred = $q.defer();
search({id: id}).
then(function (response) {
deferred.resolve(response.data.results[0]);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
to this
return search({id: id}).
then(function (response) {
return response.data.results[0];
}
$http is already returning a promise, so there is no need to use $q.defer(). Also, success() and error() has been depreciated (since 1.4.4), you should use then() instead.
Call your function like this:
search(request).then(function(data){
// successcallback
}, function(error){
// errorcallback
})
Creating a factory with $http functions will allow you to use .success and .error. But you really should be using .then.
app.factory("dataService", function($http) {
return {
search: function() {
return $http.get('path/to/api');
},
searchById: function(payload) {
return $http.post('path/to/api', payload);
},
searchMoreThings: function(payload) {
if(payload === "foo") payload = "bar";
return $http.post('path/to/api', payload);
}
}
});
You can do:
dataService.searchById(searchTerm).success().error();
Here is an actual example :
app.controller('controller',function($scope,$rootScope,$http,){
$scope.login = function(request){
var promise = $http.post(/path/to/resource, request, {
headers: {
'Content-Type': 'application/json'
}
}).then(function success(res){
//it worked, you have data in res
},function error(res){
// it failed
});
};
});

Angularjs .then is not a function

I need to call a Factory method from a Controller method in Angular. But when I call the Factory method AuthFactory.login(username,password).then(), it shows Error like this
TypeError: AuthFactory.login(...).then is not a function
Plunker code here .
I think I am missing here a concept of AngularJS promises.
For login method, you are sending simple javascript object not promise object.
function login(username, password) {
var userInfo = {
"authToken": "6a65dd0c-b35a-429b-a9c0-f5c327ec5d6f",
"id": "1445138519"
};
return userInfo; // Returning javascript object
}
In plunkr, you have some commented code as below:
// $http.post(API_URL + '/auth/login', {
// email: username,
// password: password
// })
So instead returning userInfo, return $http.post object and then use then method.
You need to wrap the response in a promise, which then resolves.
.factory('AuthFactory', function AuthFactory($http, API_URL, $q) {
'use strict';
return {
login: login
};
function login(username, password) {
var deferred = $q.defer();
deferred.resolve(
{
"authToken":"6a65dd0c-b35a-429b-a9c0-f5c327ec5d6f",
"id": "1445138519"
});
return deferred.promise;
You return the promise, but resolve the actual result. If you want it to fail, you can do a deferred.reject() which returns the error condition in your then.
Plunker
Your are returning an object, not a promise.
If you want to return a promise that resolves to that object is pretty simple:
return $q.when(userInfo);
http://plnkr.co/edit/YeE8JmqcDSKqQY969Kpo?p=preview

AngularJS: Why is .then() not waiting for the promise object to arrive

I have created the following angular service for the purpose of making the API calls.
(function (module) {
module.service('APIService',function ($http, $q) {
console.log("Reached APIService")
var deferred = $q.defer();
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
$http.get(requestURL).success(
function (data, status) {
deferred.resolve(data);
console.log("In service",status,data);
}).error(
function (data, status) {
deferred.reject(data);
console.log(status);
});
return deferred.promise;
};
this.postData = function (requestURL, requestObj) {
console.log("Reached APIService #POST", requestURL, requestObj);
$http.post(requestURL, requestObj).success(
function (data, status) {
deferred.resolve(data);
console.log(status);
}).error(
function (data, status) {
deferred.reject(data);
console.log(status);
});
return deferred.promise;
};
});
}(angular.module("MainApp")));
I have injected it in my two controllers. However, I am facing following issues:
When I call it first time in first controller, it works fine and returns the desired result. However, when I call it in second controller as follows:
APIService.getData(Config + headerURL).then(function (response) {
console.log(Config + headerURL);
console.log("header response from API", response);
},function(error) {
console.log("API call for header data failed");
});
Since my service returns a promise object, I don't expect the code inside .then() to work before the service data arrives.
However, it runs before service data arrives (no clue how). The most strange thing is that the response that I get inside .then() is not actually the response from this URL (Config+headerURL), but it is the response that was obtained from a different URL previously called in first Controller using the same APIService service.
Just to inform: the actual response from current URL do arrive at a later stage.
I am aware of asynchronous callings and I think it has something to do in this case, but I guess .then() handles it in Angular. So I am confused what is the issue here. Can anyone shed some light please?
Since the service is a singleton, you only have a single instance of the deferred object.
Once resolved, it will keep being resolved, so the next time you call, getData, it will return immediately.
You could move:
var deferred = $q.defer();
inside both your getData and postData function.
Or you could just return the promise that $http creates.
Just try this, You need to return to the http result.
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
return $http.get(requestURL) };
Looks like you have to disable cache explicitly within $http call.
$http.get({
url: requestURL,
cache: false
})
You are using an anti-pattern by creating your own promises when $http already returns a promise.
Get rid of $q altogether in this service and simply return $http
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
return $http.get(requestURL).then(function (response) {
return response.data;
console.log("In service", status, data);
}, function () {
// errror handling here
});
};

Asynchronus calls in angularjs

I am new to Javascript and Angularjs. I wanted to know , how to call a function asynchronously without waiting for it to return from it.
Please let me know and if there is some example then it would be very helpful.
Regards,
nG
Use Angular's deferred:
function myAsyncFunction() {
var deferred = $q.defer();
//..
setTimeout(function() {
deferred.resolve({ message: "resolved!" });
// or deferred.reject({ message: "something went terribly wrong!!" });
}, 1000);
//..
return deferred.promise;
}
myAsyncFunction()
.then(function(data){
// success
console.log("success", data.message);
}, function(data) {
// fail
console.log("error", data.message);
}).finally(function() {
// always
});
You can use a deferred to return a promise, you can then resolve the promise once your function is complete. Try something like this:
http://jsfiddle.net/adcoxwga/
var myApp = angular.module('myApp',[])
.service('myService', function($q, $timeout){
this.someFunction = function(){
var deferred = $q.defer(); //making a new deferred
$timeout(function(){
deferred.resolve('something'); //resolving the deferred with a response
}, 3000);
return deferred.promise; //returning a promise
}
})
.controller('MyCtrl', function($scope, myService){
myService.someFunction().then(function(response){ //calling the asynchronous function
console.log(response); //getting response
}, function(error){
console.log(error); //getting error
});
});
You have a couple of different options ahead of you, but one thing to note is that you have to use a callback or promise for asynchronous activity. Since you are using angular, I'd suggest using promise's.
Using a promise
If you are writing an http call to an api, you can use either $http or $resource. If you start to research angular providers, you will see that $http returns a promise, while $resource returns a promise but you must call it specifically to access it. For instance:
someService
function someServiceFunction () {
return $http.get('some/url');
}
someController
$scope.results = [];
someService.someServiceFunction().then(function(data) {
$scope.results = data;
});
Something to note is that the first returned function is the success scenario and if you declare a second callback, then it is the failure scenario.
If you were to use callbacks in the same scenario:
Using a callback
someService
function someServiceFunction (callback) {
return $http.get('some/url')
.success(callback);
}
someController
$scope.results = [];
someService.someServiceFunction(function(data, status, headers, config) {
$scope.results = data;
});
Here's a link to the $http provider.
Here's a link to the $resource provider.
Cheers!

Categories

Resources