Triggering the error handler for a Promise in Angular - angularjs

I'm working with $q in Angular, and trying to chain some promises together so that I only perform an action once all my Promises have been successfully resolved. However, if any one of them fails, I want to avoid performing that action. By way of example:
function getData() {
return $http.get('url').then(
function(goodResponse) {
//do stuff
},
function(badResponse) {
return $q.reject("getData failed");
}
}
function firstPromise() {
return getData().then(
function() {
//got the data
},
function(error) {
return $q.reject(new BespokeErrorObject({message: error}));
}
);
}
function performAllPromises() {
// Has access to $q
$q.all(firstPromise(), ... ).then(
function() {
// All my Promises were fulfilled successfully
console.log("All good");
},
function(error) {
// Any one of them failed. Bail.
console.log(error.message);
}
}
In my application (which is slightly more involved than this example), I'm getting the All good output rather than the contents of the Error that is thrown, which is not what I expect to happen based on my understanding of the documentation. Am I using all incorrectly?
Update: Updated to use $q.reject as suggested by Nikos, and added extra layer as in the real code.

To reject a promise from any then callback, you have to return $q.reject(xxx).
In your case:
function getData() {
return $http.get('url').then(
function(goodResponse) {
//do stuff
},
function(badResponse) {
return $q.reject("getData failed");
}
}

PEBCAK issue. The offending line is
$q.all(firstPromise(), ... ).then(
which should be
$q.all([firstPromise(), ...]).then(
A subtle, but very important difference.

Related

Mocked func returning promise with callFake doesn't enter then()

I have same method calls and am trying to write tests to mock MyService.getSoemthing in methodB(). However, it doesn't seem to work..
methodA(): any {
methodB().then(() => {
do something...
})
}
methodB(): ng.IPromise<void> {
return MyService.getSomething(a,b,c,d,e).then((result) => {
...does something with result...
doesn't returning anything
});
}
// With MyServiceMock injected
spyOn(MyServiceMock, 'getSomething').and.callFake(() => {
return $q.resolve(result); // result is a variable that has already been init
});
or
spyOn(MyServiceMock, 'getSomething').and.callFake((a,b,c,d,e) => {
return $q.resolve(result); // result is a variable that has already been init
});
// then I call my methodA and ..
methodA();
It simply won't work.. I am able to debug until the resolution of the mock, it gets to resolve the promise but never ends up inside my ** ...does something with result... **
Any ideas?

Cancel a $transitions change ui-router

I am trying to cancel a $transitions change under certain conditions using ui-router.
In my run block, I have the following code:
$transitions.onStart( { from: 'createCatalog.previewStyles'}, function(trans) {
var from = trans.from(),
to = trans.to();
previewStylesService.checkSave()
.then(function success() {
return $state.target(to);
}, function err() {
return $state.target(from);
});
});
My previewStylesService checkSave function looks like this:
function checkSave() {
var deferred = $q.defer()
if (dataChanged) {
if (confirm('Would you like to save the changes made to the catalog?')) {
catalogService.prepCatalogSave()
.then(function success() {
deferred.resolve();
}, function err () {
deferred.reject();
})
} else {
deferred.resolve();
}
} else {
deferred.reject();
}
return deferred.promise;
}
Then based on the above conditions, the $transition will either take place or will cancel. The problem is, even if the above code's promise is rejected, the state still changes to the originally requested state. How can I "cancel" the state change in this case?
I do know it is may be late to help you but It could help others.
I just ran into the same problem and after about a couple of hours of research/doc reading I came to the conclusion that $transitions.onBlaBla callbacks had a return value that could be true (the transition resume normaly), false (transition is canceled), or a promise (transitionService will wait for this promise rejection/resolve to decide if it needs to do the transition or not
You could try to return
return previewStylesService.checkSave()
to see what happens or try to do it differently with a return true/false and some other code hooks
Here is the link of hook result that is return by your onSuccess Callback:
https://ui-router.github.io/ng1/docs/latest/modules/transition.html#hookresult
Simply return false from your hook to cancel the transition:
https://ui-router.github.io/ng1/docs/latest/modules/transition.html#hookresult

Always return data from service

I have a series of services that either fetch data from an API server, or return data if it exists in local storage.
.factory('LogEntryResource', function(config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/');
})
.factory('LogEntry', function(localStorageService, LogEntryResource) {
var localLogEntries = localStorageService.get("logEntries");
return {
all: function() {
if(localLogEntries){
return localStorageService.get("logEntries");
} else {
return LogEntry.query(function(data){
localStorageService.set("logEntries", data);
return data;
});
}
},
get: function(logEntryId){
...
},
delete: function(logEntryId){
...
},
update: function(logEntryId){
...
}
}
})
The problem is that in the app controllers sometimes a promise is returned, and sometimes the data is returned, so I need to handle the return value of LogEntry.all() to either wait for the promise to resolve or to use the data.
I'm not really sure how to go about it because I can either use a .then() which works for the promise, but is undefined if it has data, or vice-versa. I know I'm doing something wrong and looking for advice how to handle this situation of dealing with either data or a promise being returned.
.controller('LogEntryCtrl', function($scope, LogEntry) {
// var data = LogEntry.all();
// var promise = LogEntry.all();
$scope.logEntry = ???
}
I'm hoping there's a nice reusable solution instead of having to do a check to see what it is every time I use this code in my controllers/routes
// trying to avoid doing this
var logEntry = LogEntry.all();
if(logEntry.isPromise){
// do promise stuff here
} else if(logEntry.isData {
// do data stuff here
}
My suggestion would be always return a promise. You can use $q.resolve() to create a shortcut for a resolved promise
.factory('LogEntry', function(localStorageService, LogEntry, $q) {
var localLogEntries = localStorageService.get("logEntries");
return {
all: function() {
if(localLogEntries){
return $q.resolve(localLogEntries);
} else {
return LogEntry.query(function(data){
localStorageService.set("logEntries", data);
// update variable also
localLogEntries = data;
return localLogEntries ;
});
}
},
In controller you always use then() this way
LogEntry.all().then(function(data){
$scope.data = data;
});

Angular conditional promises

For an angular project, I have to nest promises and I run into cases where I am not sure of what I am doing.
Here is one of my code :
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
$q.all([Action2(), Action3(), Action4()]).then(function () {
defer.resolve();
});
} else {
defer.reject("error_code");
}
return defer.promise;
});
Action1, Action2, Action3 and Action4 are working promises functions. It's a lot of promises and actions depend on conditions.
Can I do that and be sure my main function will be always resolved or rejected?
I read that we can pass promise inside resolve function.
Can I do that and is this the same as above:
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
defer.resolve($q.all([Action2(), Action3(), Action4()]);
} else {
defer.reject("error_code");
}
return defer.promise;
});
No, it is not. Your first function would stay forever pending if one of Action2(), Action3() or Action4() did "throw", and reject the $q.all(…) promise - your deferred is never resolved then. This is the most common bug of the deferred antipattern you've used here.
Your second function does mitigate this, but is still unncessary complicated. You don't need a deferred here at all! Just return the promise directly, and use $q.reject:
return Action1().then(function (data) {
if (data.condition) {
return $q.all([Action2(), Action3(), Action4()]);
} else {
return $q.reject("error_code");
}
});
Or, as this happens inside a then handler, you can also use throw "error_code".
Thanks for your answer, I can see my error on the first code version. I think it's the q.all which perturbs me.
I read the deferred antipattern. It said that we don't have to create deferred objects for no reason.
The simple case is this :
return Action1().then(function () {
return $q.all([Action2(),Action3(), Action4()]);
});
But due to the if (data.condition) I can't do it.
Is my second code the only way to do it? Am I in a case or I have to use defer?
It speaks about "promisification", but with Angular I don't know if it's a good thing (libs seem unmaintained).
Cheers,

AngularJS: Promises & conditions based on results

I have a complex promise chain, with each of the success handlers in then making some more API calls and passing on the results to the next then and so on.
I've come to a situation where, based on a condition, I may choose to stop the chain.
So, in a nutshell, my code looks like;
API.callGeneric(/* some params here */)
.$promise
.then(success(res) {
if (processFurther(res)) {
return API.callGeneric(res).$promise;
} else {
return someFunction(res); // so that the stuff inside the next successhandler still happens
}
}, failHandler)
.then(success(res) {
// do some stuff with res
// do some important stuff independent of res (announce app ready, etc.)
}, failHandler)
So, there is some stuff that needs to happen in the last step irrespective of whether or not I choose to return the promise from another API call or just an object.
How can that be done?
Solved the issue with the help of #BenjaminGruenbaum.
So, basically, I needed the successHandler of the last .then to execute in any case – at least the part of it that didn't depend on the promise passed on from earlier in the chain.
What solves this is using .finally, but there's a catch. .finally executes irrespective of where you decide to reject the promise and break the chain. And in my scenario, that wasn't what I needed. In .finally, I needed to announce that my webapp was ready (through a websocket) to the server and other clients. But that would not be ideal if the first API call itself had to be rejected.
What solved it was maintaining a measure of the progress through the promise chain, so that my handler in finally was completely aware of how much progress had been made. If this was above a certain limit for my app to be declared ready, I ignored the last promise rejection.
So basically, the solution now looks like this;
var progress = 0;
API.callGeneric(/* some params */).$promise
.then(successOne(res) {
progress++;
return API.callGeneric(res).$promise;
}, handleErr)
.then(successTwo(res) {
progress++;
if (isResPositive(res)) {
return API.callGeneric(res).$promise;
} else {
var def = $q.defer();
def.reject(res);
return def.promise;
}
}, handleErr)
.then(/* similar stuff */)
/* etc */
.finally(function () {
if (progress > limit) {
// do stuff here
} else {
// failure, don't do stuff
}
});
You can use a reject promise like $q.reject(reason) as a return value for function.
.then(function () {
return $q.reject()
}).then(function () {
console.log('never happens');
}).catch(function () {
console.log('you are going here because of the reject above');
}).finally(function () {
console.log('always happens');
});
Look at the documentation for more info.

Resources