I have my application that should open a popup ask a confirmation at the user, then make an ajax cal and close the popup.
I tried to do it using a chain of promise (I've already used it, and I remember that it should work in this way), but it seems to block after the call toreservationService.confirm($scope.object);. Now it is a fake service implemented with a setTimeout and $q just to return a promise (in future it will make the ajax call). Is this a valid code or I didn't undestood how promise works?
For the popup I choose AngularUI and the code is this:
reservationService.book($scope.object, day)
.then(function(){
var dialogOpts = {/* dialog options omitted*/}
return $dialog.dialog(dialogOpts).open();
})
.then(function(result){
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return reservationService.confirm($scope.object);
}
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
reservationService:
function confirm(){
var deferred = $q.defer();
setTimeout(function(){
console.log('Confirming');
deferred.resolve(true)
}, 500);
return deferred.promise;
}
SOLVED
change setTimeout with $timeout angular's service
Used $timeout instead of setTimeout 'cause it works togheter at the angular scope, forcing the digest phase (or use $scope.apply() inside the setTimeout).
Can you try
//skipping the first then
.then(function(result){
var deferred = $q.defer();
console.log('confirmed? ', result);
if (result){
//After this line it doesn't do nothing, also if the promise is resolved
return deferred.resolve(reservationService.confirm($scope.object));
}
deferred.resolve();
return deferred.promise;
})
.then(function(){
//this function is never executed
$scope.$emit('object:detail',{object: $scope.object});
});
For chaining then, the last then success or failure function should return a promise. As the $q documentation mentions
then(successCallback, errorCallback) – regardless of when the promise
was or will be resolved or rejected, then calls one of the success or
error callbacks asynchronously as soon as the result is available. The
callbacks are called with a single argument: the result or rejection
reason.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback or errorCallback.
Related
I'm using angularJS and I do this:
xxx.then(function (response) {
$scope.x = response.x;
$scope.y = response.y;
}, function (error) {}
);
The response come from server not instantantly. Then when the response come, I want than the scope update my value, but it does that just when I click in some button other so.
In my html I receive the informations so:
<p>{{x}}</p>
<p>{{y}}</p>
Do you know what I'm doing wrong?
This could be an issue with the digest cycle, try doing $scope.$apply() like below :
xxx.then(function (response) {
$scope.x = response.x;
$scope.y = response.y;
$scope.$apply();
}, function (error) {});
In AngularJS the results of promise resolution are propagated
asynchronously, inside a $digest cycle. So, callbacks registered with
then() will only be called upon entering a $digest cycle.
The results of your promise will not be propagated until the next digest cycle. As there is nothing else in your code that triggers the digest cycle, changes are not getting applied immediately. But, when you click on a button , it triggers the digest cycle, due to which the changes are getting applied
Check this for a clear explanation about this.
A .then method may fail to update the DOM if the promise comes from a source other than the $q service. Use $q.when to convert the external promise to a $q service promise:
//xxx.then(function (response) {
$q.when(xxx).then(function (data) {
$scope.x = data.x;
$scope.y = data.y;
}, function (error) {
throw error;
});
The $q service is integrated with the AngularJS framework and the $q service .then method will automatically invoke the framework digest cycle to update the DOM.
$q.when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
-- AngularJS $q Service API Reference - $q.when
Im using angularjs 1.4 and jasmine 2.4.
Im trying to test a function and I want to make it return a promise, so another layer above it can deal possible values.
My issue is that the function first validate the inputs. If they are not the right ones I want to return a rejected promise. Otherwise it will do whatever it has to do and resolve the promise.
Here is part of the function from the emailSvc in question:
// Function found in the emailSvc
this.sendEmail = function sendEmail(apiKey, token, email_type)
{
// Prerequisite to send email
if(!apiKey) {
return $q.when("apiKey not present.");
}
var deferred = $q.defer();
// Ajax call
serviceApiEmail.send(apiKey, token, email_type)
.then(function(data){
deferred.resolve(data);
})
.catch(function(e){
deferred.reject(e);
})
return deferred.promise;
}
And my test case is like follow:
it('should reject sending email if apiKey is not present', function(){
var rejectEmail;
var apiKey,
verifyToken = acceptedVerifyToken,
emailType = const_EMAIL_TYPE.SIGNUP;
i_emailSvc.sendEmail(apiKey, verifyToken, emailType)
.then(function(){
// It should not come here
rejectEmail = false;
}).catch(function(){
rejectEmail = true;
});
// It comes here without executing any success or fail promise handlers
expect(rejectEmail).toBe(true);
});
The issue is that when rejecting the promise the catch is never executed. I believe this is with a misconception I have with promises.
Any ideas why the catch and then are not working in here?
You are resolving the promise if the apiKey is not present when using $q.when(). This would indicate a successfully completed promise. To indicate failure you should use $q.reject i.e.
if(!apiKey) {
return $q.reject("apiKey not present.");
}
Additionally for the then or catch callbacks to be executed in your test you would usually need to trigger a digest cycle. The usual way of doing this is tests is to get a reference to the $rootScope and call $rootScope.$digest().
Are you mocking the serviceApiEmail.send method? Are you reaching the then at all? I'd check for something else then rejectEmail boolean because with Javascript your test is going to be false even if the 'then' part is not reached, so you aren't maybe even reaching the promise resolve part
Is it possible to return anything from a promise notify callback?
In the following code ServiceB.Start returns a deferred promise where the deferred is defined on ServiceB:
ServiceB.Start(action).then(
function() {console.log("Success");},
function() {console.log("Failed");},
function (notifyObject) {
var deferred = $q.defer();
//do something time consuming
$timeout(function() {
if (notifyObject.success) {
deferred.resolve({ message: "This is great!" });
} else {
deferred.reject({ message: "Really bad" });
}
}, 5000);
console.log(notifyObject.message);
return deferred.promise;
}
);
var notifyReturnValue = ServiceB.deferred.notify(notifyObject);
notifyReturnValue.then(
function() {
//do something else
ServiceB.deferred.resolve(data);
}
);
}
notifyReturnValue seems to be undefined. Is there a way to return something from a deferred.notify()?
Yes, you can return a value from notify callback. It works similar to returning value from success/error callbacks. The returned value is passed on to the next notify callback in line. However, as the documentation states, you cannot influence resolution/rejection from notify callback. This makes sense, because notify may be called multiple times, while a promise may be resolved/rejected only once.
When you call then, you get a new Promise. That is because it's a means of chaining asynchronous actions. The new promise is resolved when not only the original action, but also the callbacks passed into then (which can be asynchronous as well) are resolved.
See this demo of passing on notification values (with your console open).
It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
So this may be trivial, but I am doing some proof of concept stuff and trying to reject a promise in the middle of a promise chain, but I am not getting the results I would expect.
app.controller('MainCtrl', function($scope, $q) {
var def = $q.defer();
def.promise
.then(testPromiseReject())
.then(
function(){
console.log("SUCCESS")
},
function(){
console.log("FAIL")
});
def.resolve();
function testPromiseReject(action)
{
return $q.reject()
}
});
I think I am creating a promise that I initially resolve, but in the first then I have a function that I am trying to reject the rest of the promise chain. The above code prints "SUCCESS" to the console. Why is it not rejecting the rest of the chain?
There's a problem with this line...
.then(testPromiseReject())
It just needs the ()s removed so it's not executed immediately...
.then(testPromiseReject)
Fiddle... http://jsfiddle.net/5LVEE/1/