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,
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 have a following piece of code:
function getData(foos, bars) {
var generated = {};
return $q(function(resolve, reject) {
var promises = _.map(foos, function(foo) {
return _.map(bars, function(bar) {
return someServiceCall(foo, bar).then(function(data) {
_.set(generated[foo.id], player.id.toString() + '.data', data);
});
});
});
// Join all promises in to a final resolve
$q.all(promises).then(function() {
resolve(generated);
}, reject);
});
}
What I want to achieve is to have all the someServiceCall-s and it's success handlers finished by the time resolve(generated) is called. When I debug this part of the code in the developer toolbar, resolve(generated) is called before the success handler of someServiceCall is called for every promise.
Note: This not always breaks the functionality, as the objects are passed as a reference, so the data is set even if resolve was already called, but I think the functionality would be clearer if all those calls were finished by the time resolve is called.
I just realized my dumb mistake. The two nested maps resulted the following structure:
var promises = [[Promise, Promise][Promise, Promise, Promise]];
While $q.all expects an array of promises:
var promises = [Promise, Promise, Promise, Promise, Promise];
I could easily solve this by replacing the first map call by flatMap:
return _.flatMap(bars, function(bar) {
It's still strange for me that $q.all silently just resolved the promise without an error or warning that the data format is not appropriate.
I hope I can help someone who runs into this problem in the future.
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.
I've tried to strip out the details and make this fairly generalized...
Using 1.2 rc2 my code worked fine, after updating to 1.2 stable and correcting for $parse changes I've run into a binding problem. Before the update, the following code worked without any issues. updateChildObject() gets called from the html page.
.when('/the-page/', {
controller: function($scope, serviceResults, FactoryService) {
$scope.object.childObject = serviceResults;
// this function used to work. Now assigns the function to the
// scope rather than the results
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
},
resolve: {
serviceResults: function(FactoryService) {
return FactoryService.getSomethingFromServer(args);
}
}
Since this is failing now ($scope.object.childObject appears to get set as the function and not the results) I believe the appropriate way to solve it is through a promise. (Note, the service itself is using a promise successfully.) However, I'm having difficulty getting the $scope to update when the promise is resolved.
I believe the following code is along the right track. $q is injected in the controller.
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function() {
return FactoryService.getSomethingFromServer(args);
});
$scope.object.childObject = defer.resolve();
};
...
So can anyone tell my what I'm doing wrong here? Promises are just one of those things that haven't really clicked for me yet.
Just as an alternative to your answer: you say FactoryService is already successfully using a promise, and in that case it seems like you don't need an additional promise in updateChildObject too. You could update FactoryService.getSomethingFromServer(args) to return a promise (i.e. with return defer.promise; at the end and defer.resolve(results); in the async bit), and then simplify updateChildObject to just:
$scope.updateChildObject = function(args) {
FactoryService.getSomethingFromServer(args).then(function(results) {
$scope.object.childObject = results;
}
};
Also, it's worth knowing that Angular 1.2 intentionally breaks automatic promise unwrapping that was in earlier versions: https://github.com/angular/angular.js/issues/4158 . It used to be the case that this code
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
would work identically to the one above (assuming getSomethingFromServer returns a promise), but not anymore. This might be the issue you're running into with 1.2
Figured out what I was doing wrong. Definitely was a promise issue in that I just wasn't using them correctly. The following solved it:
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function(results) {
$scope.object.childObject = results;
});
defer.resolve(FactoryService.getSomethingFromServer(args));
};
...
So defer.resolve calls what's to be resolved. promise.then() passes the results to the next action. So simple.