I'm creating an eCommerce store that will have to support multiple payment gateways. One of these payment gateways include the National Australian Bank, which doesn't include an API which communicates with HTTP codes and JSON. Instead, it responds with just text and a status of 200 even if a transaction fails.
In my application I'm using the $http provider to handle these responses, but it will always fire the success function, thus I need to somehow intercept the response before deciding if it's a success or failure.
Here's how it's set up:
// Controller
PaymentService.submit(payment_info).then(function(resp){
// Do stuff on success
})
// PaymentService#submit
payment.submit = function(data) {
var strategy;
// general payment codez
if (CURRENT_COUNTRY == 'au') {
strategy = PaymentStrategy.nab.submit(card.number, card.cvv, card.exp_year, card.exp_month);
}
return strategy;
}
// PaymentStrategy.nab#submit
service.submit = function(number, cvv, exp_year, exp_month) {
// payment codez specific to NAB
return $http.post(resource_path, $httpParamSerializerJQLike(payload), { headers: headers })
}
So the issue is that PaymentService.submit is the promise that was passed back from PaymentStrategy.nab. However that promise will always fire the success callback because the response is always 200 even if the payment failed (that's house National Australian Bank's current system works. It's supposed to be submitted with an HTML form).
I want the PaymentService.submit method to work for any payment gateway - so I need the promise that is passed back to always behave the same. The only place these kinds of unique configurations should occur is within their own strategies.
With all that said, how can I get a response from NAB, normalize that response, and then back another proper response back down the chain?
To convert a successful promise to a rejected promise, throw a value in the success handler. To convert a rejected promise to a successful promise, return a value to the .catch handler.
return $http.get(url).then(function(response) {
if (response.data.transactionFailed) {
//THROW to convert to rejection;
throw response;
};
//OTHERWISE return to chain response
return response;
});
The .then method of a promise returns a new promise that resolves to what is returned (or rejected with what is thrown).
For more information
Angular execution order with $q.
You're Missing the Point of Promises
Related
I have a very large angularjs app, that sells stuff and has filters
It seems that we need to support people on flaky connection.
That means that if user selects 'used product' filter and then he unselects 'used product', there will be a 2 calls to the server via $http.
$http.get("reloadresults?used=true", function (response) { $scope.items = response items; }); at 12:03:04 hours
$http.get("reloadresults?used=false", function (response) { $scope.items = response items; }); at 12:03:05
Now, image there is a bottleneck or something and the first call with 'used=true' returns last, then there is a problem with the filters.
I know there is a $http interceptor in angularjs, based on promises, how would i fix this problem? So that requests are processed in the order they are sent, meaning 'used=true' and only then used=false.
Edit: cant block thread, cant refactor, just need for the promises to fullfil in the order they were first sent. I think ill post answer later.
I din't understand your question well but i think you are looking for
$q.all(valid_request)
You could indeed ensure that success handlers are called in the correct order by forming a queue (a promise chain) however it is simpler, and better in this case, to nullify the previous request each time a new request is made.
There's a number of ways in which this could be achieved. Here's one ...
function cancelPrevious(fn) {
var reject_ = null;
return function(x) {
if(reject_) reject_(new Error('cancelled'));
return Promise.race(fn(x), new Promise(function(_, reject) {
reject_ = reject; // if reject_ is called before fn(x) settles, the returned promise will be rejected.
}));
};
}
which allows you to write ...
var get = cancelPrevious(function(str) {
return $http.get(str);
});
... and to make requests from any number of event threads :
get('reloadresults?used=true').then(function(response) {
// This function will be reached only if
// $http.get('reloadresults?used=true') fulfills
// before another get() call is made.
$scope.items = response.items;
});
...
// This call causes the then callback above to be suppressed if not already called
get('reloadresults?used=false').then(function(response) {
$scope.items = response.items;
});
Notes :
http requests are not canceled. Instead, the succuss path stemming from each request is made "cancelable" by racing against a rejectable promise.
side-effects included in the function passed to cancelPrevious() may be executed; in general, don't include such side effects.
I am remember researching this awhile back and coming up empty handed and I still can't find any good info on this - why does there appear to be a $q.reject method but not an $http.reject method?
For example, in real life we might have:
unfollow: function (userId) {
if (!AuthService.isLoggedIn()) {
//$location.url('/login');
window.location.href = '/login';
return $q.reject({error: 'no logged-in user, but non-existent user could still click a follow button?'});
}
else {
return $http({
method: 'PUT',
url: ConfigService.api.baseUrl + '/v1/users/add_unfollow/by_id/' + userId
});
}
}
I would rather uses the relevant $http.reject instead of $q.reject, but that doesn't seem to work.
Since $http returns a promise in one leg of the conditional...the function itself needs to return a promise in the other leg also.
Using $q.reject() is simply a shortcut to return a rejected promise.
Without it, any place that calls unfollow().then() wouldn't have a then() method if a promise wasn't returned
$http would wrap the http calls and return a promise. The promise would rejected if the actual http request is failed, and adding a reject method would not make much sense, rather it should be in the promise.
In your example you would not even need to call the $http service to reject the request.
I am stuck with the approach I am taking probably due to my lack of knowledge about angular promises VS restangular promises, etc.
I have an AngularJs application with TypeScript (although typescript is mostly irrelevant here and the same applies to any javascript). These are the players:
controller: it gets injected a service, through this service the controller can send a POST to an API
service: it wraps restangular. The idea is that this service does not expose any restangular functionality to the controller. It abstracts the controller from knowing how to save an item. It has a method that accepts an object and returns an angular promise.
export interface IRegistrationService {
addRegistration(model: registration.BusinessRegistration): ng.IPromise<void>;
}
Restangular error interceptor: it handles Http Responses with status 400 coming from an API because they are validation errors and transforms them in a custom object. The idea is that eventually the controller can either succeed saving an item (posting it through the service) or get a validation error (that comes from this interceptor).
This is what I have so far:
The restangular error interceptor
restangularProvider.setErrorInterceptor((response: restangular.IResponse, deferred: ng.IDeferred<any>, responseHandler: any) => {
if (response.status === 400) {
let validationError: myTsd.IApiValidationErrors = getAsValidationError(response.data);
// How to pass this validationError as an errorCallback to the controller?
//deferred.notify(validationError);
//deferred.reject(validationError); //this stops the chain
//return true; // if error not handled. But where to put the validationError?
//return false; // if error handled. But where to put the validationError?
}
});
The service that abstracts the controller from knowing anything about restangular Notice that it should return an angular promise, not a restangular promise.
public addRegistration(model: registration.BusinessRegistration): ng.IPromise<void> {
return this.restangular.all("registration")
.post<registration.BusinessRegistration>(model)
.then(() => {
console.log("Registration posted successfully");
}, (error: any) => {
//if I get the object here, how to make it available in the errorCallback of the controller caller?
}, (notify: any) => {
//if I get the object here, how to make it available in the errorCallback of the controller caller?
});
}
The controller that uses that service but knows nothing about restangular
//public static $inject = ["app.services.RegistrationService"];
//.. controller code
this.registrationService.addRegistration(this.model)
.then(() => {
console.log("model posted successfully in remote API")
}, (error: myTsd.IApiValidationErrors) => {
// if there was any validation error I need the object here
console.log(error);
});
How should I chain everything? My "only" requirements are:
the logic to create that object is in a central place like the setErrorInterceptor, and it should distinguish between http responses 400 or any other. If the response is neither 2xx or 400 it can handle the error or pass it to the service that uses restangular. It doesn't matter
the service that uses restangular must allow the controller to either succeed or have a callbackError with the custom validation error object. It abstracts the controller from everything else.
Thanks a lot!
I don't fully understand the docs here https://github.com/mgonto/restangular#seterrorinterceptor and whether there is something else other than notifying or rejecting that I could do.
Restangular's .setErrorInterceptor() is a rather odd beast, which, as far as I can gather, won't do what you want it to do.
It can be made to sense error code(s) (eg your 400) and do stuff when that condition arises, but has no further ability other than to return false (block) or return anything else (not block).
The non-blocking action allows the promise chain to take its natural, unintercepted course.
The blocking action inhibits both the error path and the success path of the promise chain.
Therefore think of .setErrorInterceptor() as a "selective blocker", not a "filter" or a "catch", and contrast it with promise.catch() behaviour, by which :
an error state can be converted to success by returning some value/object,
the error can be rethrown, or some new error can be thrown, keeping the promise chain on the error path.
The inability of .setErrorInterceptor() to propagate anything other than the original error seems to mitigate against it in favour of a named "catch handler" (eg. getAsValidationError() or a function that wraps getAsValidationError()) that can be included wherever relevant. That should give you the feature you require.
The only problem I can foresee is getting the catch handler to recognise the "400" condition - possibly simple - requires research.
Don't get too hung up on Angular promises versus Restangular. They should inter-operate.
I've been tracking down the reason why I cannot see the response from my webapi in angularjs using $http if the status code is less than 200 and greater than 300.
I've debugged angular.js to the point where I understand whats happening (basically its discarding the promise that contains the data i care about and creating a new one without it if the status codes are not deemed a success)
-see code at https://github.com/angular/angular.js/blob/master/src/ng/q.js#L270-L280
this.$$state.value (holds the http respone) is lost at this point for some reason.
Does it make sense to omit the original values? I'm not going to submit a bug report without asking here if I'm right or wrong.
The reason behind all this is that I am processing some credit card info on the server side. If the processor says its an invalid card, I wouldn't think it would be a 200 code, would it? See the answer to this question..... Suggesting 400 for business rules. This still fails in angularjs.
What is the appropriate HTTP status code response for a general unsuccessful request (not an error)?
Also, FYI, httpInterceptors do not work either, since they are utilized after this 'promise replacement' occurs.
From the Docs:
A response status code between 200 and 299 is considered a success status and will result in the success callback being called. Any response status code outside of that range is considered an error status and will result in the error callback being called.
— AngularJS $http Service API Reference - General Usage
The $http service rejects responses outside of the range of 200 to 299 but it does not "discard the message response". Both the resolve and the reject methods of the $http promise are invoked with the entire response object.
This means all of the data is available in the rejection handler.
var dataPromise = $http(configObject)
.then (function onFulfilled(response) {
//return data for chaining
return response.data;
})
.catch (function onRejected(response) {
if (response.status == 400) {
console.log("400 Bad Request");
console.log("REASON:", response.data);
//chain with default data
return defaultData;
} else {
//throw to chain rejection
throw response;
}
});
In the above example, the rejection handler logs the response for messages with status 400 and converts the rejection to fulfilled with default data. All other status responses get chained as a rejection.
Data is not discarded; it is available and can be used in the rejection handler.
For more information on chaining promises, see Angular execution order with $q.
Do you write status codes when you use promise callbacks then, catch and finally after the $resource call.
Here is what I would check for :
MyRestAPI.XXX({resource}, {data}, MyCallbackFunction).$promise.catch(function(response) {
//should be called upon error
//check response.status for HTTP code.
}).then(function() {
// OK
});
in AngularJs 1.2.x, the docs only provide a rejection object, how can we see the actual response object? (to see things like http status code)
edit: here's an example from the docs:
// register the interceptor via an anonymous factory
$httpProvider.interceptors.push(function($q, dependency1, dependency2) {
return {
'responseError': function(rejection) {
// do something on error
if (canRecover(rejection)) {
return responseOrNewPromise
}
return $q.reject(rejection);
};
});
that example shows some unknown rejection object (no docs on what it's members are). the old (deprecated responseInterceptors allow query of the response object. (check for status=401, for example) how are you supposed to query for 401 service errors with the new interceptor functionality?
i am coding my usage of this to the point of being able to run it,
and so i set a breakpoint, and see that the "rejection" object has a .status property.
if a server doesn't exist, it will == 0
otherwise, it seems to return the http status code. (i see 404 so far)
I havent coded a real service point so i'll update this answer and/or accept it once i get that done.
update: yes, the .status field returns the http response status, and 0 if server not found. so this is the valid answer!