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.
Related
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
I am struggling with chaining promises using $timeouts. I would like to have a "$timeout(myFunction,1000).then()" function that fires only when ALL chained timeouts returned by myFunction are resolved.
This code snippet contains different stuff I tried and I would like to achieve:
$timeout(myFunction,1000).then(function(myPromise) {
console.log("I would like this message to appear when ALL chained promises are resolved, without knowing in advance how many chained promises there are. In this case this would be after 1500 ms, not 1000ms")
myPromise.then(function()) {
console.log("with this code patern I get a message after 1500ms, which is what I want, but this does not work anymore if myOtherFunction would return a third chained $timeout")
}
})
myFunction = function() {
console.log("hi, i am launching another timeout")
return $timeout(myOtherFunction, 500)
}
myOtherFunction = function () {
console.log("1500 ms have passed")
}
How should I fix my code? Thanks!
Return promises to the success handler:
$timeout(null,1000).then(function() {
console.log("It is 1000ms");
var delay = 500
return myPromise(delay);
// ^^^^^^ return promise for chaining
}).then(function() {
console.log("this happens after myPromise resolves");
});
function myPromise(delay) {
promise = $timeout(null, delay);
return promise;
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference -- Chaining promises;
Inspired by the answer of georgeawg I created my custom timeout function that returns the promise returned by fct, instead of the promise returned by $timeout. I did this to keep the $timeout syntax.
vm.customTimeout = function (fct, timeout){
return $timeout(fct, timeout).then(function(myReturnedPromise){
return myReturnedPromise
});
}
This function is sufficient to solve my problem above. I can chain as much customTimeouts I want.
Example :
vm.customTimeout(myFunction,1000).then(function() {
var activity1 = anyFunctionReturningAPromise(100);
var activity2 = anyFunctionReturningAPromise(1000);
return $q.all([activity1, activity2])
console.log("Without knowing the content of myFunction, I am 100% sure that
every single chained promise retuned by myFunction is resolved before
executing this code, which is quite nice!")
}).then(function(){
console.log("executes when customTimeout, activity1 & activity2 are all resolved.")
})
anyFunctionReturningAPromise = function(delay) {
return vm.customTimeout(myFunction, delay)
}
Feel free to comment what you think of it.
I hope this will be useful for someone else :)
I am trying to retrofit spinners into my app.
I'm expecting to set a loading=true variable when I start async events, and set it false when the call returns.
Then in my view I can do
<span><i class="fa fa-spinner" if-show="vm.loading"></i><span>
I was hoping to find async calls of the form success, failure, finally.
The first controller I opened up makes a call in a form I don't understand. I don't even know what to call it, so I have no idea how to research and explore it.
$scope.login = function () {
if ($scope.form.$valid) {
authService.login($scope.loginData).then(function (response) {
$location.path("/dashboard");
},
function (err) {
toastr.error(err.error_description);
});
}
};
What I see here is an if statement, followed by a comma, followed by a function.
Uhh... is that some form of try/catch I've not encountered before?
I can't just add a finally on the end...
The reason I'm asking the question here is because I don't even know how to research this.
Ultimately the question I'm trying to answer is: what form of async call can I use so that I have a place to first activate the spinner, and then deactivate it?
Ah. OK. It's a standard promise - just confusingly formatted. I overlooked the .then that's on the same line.
$scope.login = function () {
if ($scope.form.$valid) {
$scope.loading = true;
authService.login($scope.loginData)
.then(function (response) {
$location.path("/dashboard");
},
function (err) {
toastr.error(err.error_description);
})
.finally(function(){
$scope.loading = false;
}
);
}
}
Found it here:
How to always run some code when a promise is fulfilled in Angular.js
Have a scenario where we have a loop and inside the loop we need to call a http service to get information about each item in the loop. Then based on the result of the service call we need to evaluate and do other work then continue on with each element in the loop. I understand this will not work as coded due to the sync nature of the service call and that the service call itself is a promise. Just looking at the best angular way to accomplish this. In the past I've used $q.all but I'd have to make multiple loops it seems to accomplish using $q.all.
_($scope.searchResult)
.each(function (results) {
var specialInfo = myService.getInfo(results); // http service call
if(specialInfo.length > 0){
// Do something
}
else
{
// Do something else
}
});
Please note to anyone responding I need the service to return before moving on as I will be showing a modal if conditions are met. The code above is pseudocode, I know the .Then is missing on the getInfo but you get the point. Each of the loops could potentially require user input to move on before reviewing the next item in the loop.
Restructure your code so there is no loop, but recursive async calls:
var currentIndex = 0;
function processNext() {
if (currentIndex >= $scope.searchResult.length) {
return;
}
var next = $scope.searchResult[currentIndex++];
myService.getInfo(next).then(function (response) {
var specialInfo = response.data;
if (specialInfo.length > 0) {
// something
} else {
// something else
}
processNext();
});
}
processNext();
Alternatively, you could fetch all promises first and then process them one at a time. Keep in mind that this method wouldn't be advised if doing async processing on the responses (like waiting for input from a modal or executing subsequent requests):
var promises = $scope.searchResult.map(function (result) {
return myService.getInfo(result);
});
$q.all(promises).then(function (responses) {
responses.each(function (response) {
// do stuff
});
});
To access the result of a promise, you use then():
_($scope.searchResult)
.each(function (results) {
myService.getInfo(results).then(function(response) {
var specialInfo = response.data;
if(specialInfo.length > 0) {
// Do something
}
else {
// Do something else
}
});
});
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.