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
Related
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
I have a function that works absolutely fine. I just want to return true or false depending on the promise.
//I want this function to return a simple true or false!!!
function isAppOnline() {
var is_connected = connectivityMonitor.isInternetConnected();
is_connected.then(function(result) {
console.log('INTERNET_CHECK_API : app online');//works fine
return true;//this is not being returned
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
return false;//this is not being returned
});
}
But when I call this function,
is_online = isAppOnline();
is_online is always undefined . Why is the function not able to return a simple boolean value?
Update :
This is what I am trying to do :
I simply want to open a popup which notifies the user that he is offline. I am calling the function isAppOnline periodically after 10secs. This function is already using a promise in my factories . I dont want to overcomplicate things but its important to me that this function returns a boolean so based on this,I can take my actions accordingly.
EDIT: For ES2017
If your one of the lucky souls who gets to use ES2017 then you can use the new await/async keywords. They are pretty brilliant and allow you to write async code that reads synchronous. (It is still promises under the hood, just unboxing of them).
function isOnline() {
return Promise.resolve(true);
}
async function Main() {
const online = await isOnline();
console.log(online);
}
Main();
fidle
Because it is asynchronous. Your isAppOnline method returns before your promise has resolved.
I presume is making some form of AJAX call to check network connectivity so there it will have to wait for it to respond. JavaScript is single threaded, if that thread locked up waiting for that request to respond so it could be synchronous your whole JavaScript would pause until it returns. Not good.
So if you want the caller of isAppOnline to know the result you have to options. Either passing it a call back or return the promise (better option)
function isAppOnline(cb) {
var is_connected = connectivityMonitor.isInternetConnected();
is_connected.then(function(result) {
console.log('INTERNET_CHECK_API : app online');//works fine
cb(true);
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
cb(false);
});
}
//better option
function isAppOnline() {
return connectivityMonitor.isInternetConnected().then(function(result) {
console.log('INTERNET_CHECK_API : app online');//works fine
return true;
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
return false;
});
}
//used as
isAppOnline().then(function (isOnline) {
console.log('Is it online?', isOnline);
});
Promise works differently with simple statements because they might return later, so you might need to rethink the flow of your program to handle different initial and final states.
function isAppOnline() {
var is_connected = connectivityMonitor.isInternetConnected();
return is_connected.then(function(result) { // add return here to return the whole promise
console.log('INTERNET_CHECK_API : app online');//works fine
return; //this will resolve the promise returned
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
$q.reject(); // this will reject the promise returned
});
}
You will have to handle changing states in your calling controller (it might be false for a short while then turned to true, etc). You might consider using a loading state to prevent misdirecting users.
is_online = false;
isLoading = true;
isAppOnline().then(function() {
is_online = true;
}, function() {
is_online = false;
})
.finally(function(){
isLoading = false;
});
Your question indicates a pretty deep need for further exploration of Javascript's asynchronous processing. I recommend reading: How do I return the response from an asynchronous call?
There are hacks you can implement to force a method to return synchronously, but angular is built in a way that honors promises almost everywhere. If you could give us some more context on what you're trying to accomplish we may be able to help you write your code in a way that takes advantage of the promise itself.
You could try the following (this is untested code since you didn't provide a working script):
function isAppOnline() {
var defer = $q.defer();
var is_connected = connectivityMonitor.isInternetConnected();
is_connected.then(function(result) {
console.log('INTERNET_CHECK_API : app online');//works fine
defer.resolve(true);
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
defer.resolve(false);
});
return defer.promise;
}
And call it like this:
var is_online = false;
isAppOnline().then(function(data){
is_online = data;
});
Or, pass the is_connected object directly, than you do better error handling:
function isAppOnline() {
var defer = $q.defer();
var is_connected = connectivityMonitor.isInternetConnected();
is_connected.then(function(result) {
console.log('INTERNET_CHECK_API : app online');//works fine
defer.resolve(is_connected);
}, function(error) {
console.log('INTERNET_CHECK_API : app offline');//works fine
defer.reject();
});
return defer.promise;
}
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,
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.
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.