I'm an newbie to angular promise. I just wanted to know, how to resolve the promise synchronously. For example,
var app = angular.module("app", []);
app.service("githubService", function($http, $q) {
var deferred = $q.defer();
this.getAccount = function() {
console.log(2)
return $http.get('https://api.github.com/users/haroldrv')
.then(function(response) {
// promise is fulfilled
deferred.resolve(response.data);
return deferred.promise;
}, function(response) {
// the following line rejects the promise
deferred.reject(response);
return deferred.promise;
});
};
});
app.controller("promiseController", function($scope, $q, githubService) {
console.log(1)
githubService.getAccount()
.then(
function(result) {
console.log(3)
// promise was fullfilled (regardless of outcome)
// checks for information will be peformed here
$scope.account = result;
},
function(error) {
console.log(3)
// handle errors here
console.log(error.statusText);
}
);
console.log(4)
});
In the above code, the values are printed in the following order 1,2,4,3. That is the service is called and getting the response synchronously. But before it resolve that http promise it received, it reaches the next line. I tried using another defer inside the response, but it doesn't work. So how to reach '4' after '3'? Here is the plunker link, http://plnkr.co/edit/AI8OJAgqFDVXb1fRYbix?p=preview
Any help on this would be greatly appreciated.
You can't, that's the nature of async calls. Any code you want to execute after a call has finished, must be placed inside the callback. The 4th log statement 'skips' the async call and is thus executed immediately.
A tip: creating a deferred object is always a code smell: 99% of the times you really don't need it. For example, you can write your service code like this and it will do exactly the same thing, but your code is much shorter:
app.service("githubService", function($http, $q) {
this.getAccount = function() {
console.log(2)
return $http.get('https://api.github.com/users/haroldrv')
.then(function(response) {
return response.data;
});
};
});
For a truly excelent explanation of promises, check this blog post by Nolan Lawson.
In your plunker, If you return the $http call then you don't have
to call .then() in the service it will return the promise to the controller.
If you want to handle response there you should use .success() and
.error() callbacks there.
Now coming to your console.log(4) in its position it will never get
executed because it is defined after githubService.getAccount() and
will be executed this way immediately after it because JavaScript is
asynchronously. The githubService.getAccount() will just register
its callback and will move on.
I've created a plunker for you to better understand promises in javascript/angular.
Hope this helps
Related
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);
}));
I am a bit confused about what the statement
return $q.reject(response);
does inside the responseError interceptor.
I have gone through this article on webdeveasy and one on angular docs but they haven't helped.
Here is my code (for reference):
(function () {
"use strict";
angular.module('xyz').factory('errorResponseInterceptor', ['$q', 'toaster', function ($q, toaster) {
return {
...
...
...
responseError: function (response) {
...
...
//some logic here
...
...
if (response.status == 500) {
toaster.error({ title: "", body: response.statusText });
return $q.reject(response);//what does this statement does and why do we need to put it here?
}
return response;
}
};
}]);
}());
My Question is:
Why do we need to write return $q.reject(response)?
How does that line affect the angular app (what does it do)?
The $q object in Angular allows us to define a promise and handle the behaviour associated with a promise. An example of how we might do this is:
var q = $q.defer()
//do something
if(success){
q.resolve()
} else {
q.reject()
}
return q
What we are doing here is defining a promise object and assigning it to the variable q. Then we perform some operation and use the result of that to determine whether the promise returns successfully or not. Calling .resolve() on the promise object is indicative of the fact that the promise has returned correctly. By the same token calling .reject() on the promise is indicative of the fact that is has failed.
If we consider how we actually use a promise:
SomeFactory.getSomething().then(function(data){
//gets called if promise resolves
}, function(error){
//gets called if promise rejected
}
So in the example you have provided, we are checking to see if the response has a 500 error code, and if it does, we are rejecting the promise thus allowing us to handle the error.
In a responseError interceptor you have two choices with regards to what you return. If you return $q.reject(response) you are continuing the error handling chain of events. If you return anything else (generally a new promise or a response) you are indicating that the error has been recovered from and should be treated as a success.
With regards to your two points:
1- You need to write that line to indicate that the response should still be considered an error.
2- The effect on the angular app is that the error callback will be called instead of the success callback.
I have a promise that runs without a problem when it runs during application start, e.g.
myPromise
.success( function(data) { $scope.myvariable = data })
.error( function(msg, code) { console.log("msg: " + msg + "\nCode" + code) });
However if I try to run the promise dynamically, let's say when a button is clicked, (1) the promise executes successfully but none of my variables are updated.
running apply or digest only produces the following error: $digest already in progress
$scope.getContent = function() {
myPromise
.success( function(data) {
$scope.myVariable = data; //THIS WORKS
console.log(data); //THIS WORKS
})
}
//Running the below code afterwards still produces a null value
console.log($scope.myVariable);
This is what we called as async world.
When you are waiting for the callback of your promise the statement console.log($scope.myVariable); already executed but still you don't have any value in it.
In that case you can use $watch if you want to get it value outside.
$scope.$watch('myVariable',funciton(newVal){
console.log(newVal);
});
Little Detail:-
myPromise is invoked and waiting for the response in the meanwhile the statement console.log($scope.myVariable); after it executed which obviously doen't have any value for $scope.myVariable inside it (nobody gave it :-P). So when response came back it call the success or error method and initialize the value to your variable $scope.myVariable and print it.
Here your console.log($scope.myVariable); statement executes before success callback so here you need to do .then() chaining or apply watch on scope variable.
The reason your console.log, which comes after your promise logs null is because it is executing before your promise returns. Even though the function using the promise has run, the success part of the code has not fired yet. The code hits the promise, makes the calls, creates the promise object, and moves on. When the promise returns, it fills in the empty promise object with the returned data. This is happening after your
console.log($scope.myVariable);
A good way maybe to handle it is to store the returned value in a variable. Inside the success set the variable to the returned data, then use a function like
$scope.myVariable = undefined;
$scope.getContent = function() {
myPromise
.success( function(data) {
$scope.myVariable = data; //THIS WORKS
console.log(data); //THIS WORKS
})
}
function checker() {
if($scope.myVariable) {
console.log($scope.myVariable);
}
}
Then you can call that function as needed.
Depending on what you are doing and when your getContent function needs to run, you may want to use this with ui-router, and use resolve, which will run these functions before the page loads, thereby ensuring you have data to work with when the DOM loads.
AngularJS UI-Router: preload $http data before app loads
I think promise object makes call only once,when controller is initialized
you have to reinitialize controller,to get new updated values from server
A. Call sequence will be as follows when DOM ready:
1. $scope.getContent will get initialized.
2. then execution : console.log($scope.myVariable);
B. Async call:
1. If success then below statement will get executed.
$scope.myVariable = data; //THIS WORKS
Above point A and B are independent. Execution is asyc here.
Hope this will help you understand. Enjoy.
Maybe this can help :
// a service providing data with async call
app.service('myService', function(elasticQuery) {
this.getData = function (a,b,c) {
// don't forget the 2 return
return elasticQuery.search({
// make ajax call
}).then(function (response) {
return response
});
};
});
// in my controller
// scope.getData is lunch with ng-click in the view
$scope.getData = function(a,b,c){
myService.getData( a,b,c ).then(function (data) {
$scope.myVariable = data;
});
};
In angular code, I have a chained promise like this:
// a function in LoaderService module.
var ensureTypesLoaded= function(){
return loadContainerTypes($scope).then(loadSampleTypes($scope)).then(loadProjectList($scope)).then(loadSubjectTypes($scope));
}
Each of these functions return a promise, that loads things from a resource and additionally modifies $scope on error and success, e.g.:
var loadProjectList = function ($scope) {
// getAll calls inside a resource method and returns promise.
return ProjectService.getAll().then(
function (items) {
// succesfull load
console.log("Promise 1 resolved");
$scope.projectList = items;
}, function () {
// Error happened
CommonService.setStatus($scope, 'Error!');
});
};
I intent to use in in a code in a controller initialziation as such:
// Part of page's controller initialization code
LoaderService.ensureTypesLoaded($scope).then(function () {
// do something to scope when successes
console.log("I will do something here after all promises resolve");
}, function () {
// do something when error
});
However, this does not work as I'd like to. Ideally message "I will do something here after all promises resolve" must appear after all promises resolve. Instead, I can see that it appears earlier than messages from resolved promises within functions that are listed ensureTypesLoaded.
I would like to create a function ensureTypesLoaded such, that:
it returns a promise that is resolved when all chained loads are resolved;
if any of "internal" promises fail, the function should not proceed to next call, but return rejected promise.
obviously, if I call ensureTypesLoaded().then(...), the things in then() must be called after everything inside ensureTypesLoaded is resolved.
Please help me with correct building of chained promises.
I thinks that problem is in your loadProjectList function. Because .then() should receive function which is called at resultion time. Usually is function returning chain promise.
But in your case you call all load immediately in parallel. It's little complicated with $scope passing. But I think your code should appear like this
//this fn is called immediatly on chain creation
var loadProjectList = function ($scope) {
//this fn is called when previous promise resolves
return function(data) {
//don't add error handling here
ProjectService.getAll().then(...)
}
}
This cause serial loading as you probably want. (just notice: for parallel excution in correct way $q.all is used)
Finally you should have error handler only in ensureTypesLoaded, not in each promise.
I have read up on Kris Kowal's Q and angularjs $q variable for a few hours now. But for the life of me I can't figure out how this works.
At the moment I have this code in my service:
resetpassword: function (email, oldPassword, newPassword) {
var deferred = $q.defer(); //Why am I doing this?
var promise = auth.$changePassword(email, oldPassword, newPassword); //$changepassword in angularfire returns a promise
console.log(deferred); //Object with resolve, reject, notify and promise attrs
var rValue = promise.then(function(){
//successcallback
return 1; //if changepassword succeeds it goes here as expected
}, function(error){
//errorcallback
return error.code; //if changepassword fails (wrong oldpass for example) it goes here, also works
}
);
deferred.resolve(promise); //Should I do this? I thought my promise was resolved in the callbacks?
console.log(rValue); //This outputs another promise? Why not 1 or error.code, how the am I ever going to get these values??
return rValue; //I want 1 or error.code to go back to my controller
},
var deferred = $q.defer(); //Why am I doing this?
It's the deferred anti-pattern that you are doing because you don't understand promises. There is literally never a good reason to use $q.defer() in application logic.
You need to return a promise from your function:
resetpassword: function(email, oldPassword, newPassword) {
return auth.$changePassword(email, oldPassword, newPassword)
.then(function() { return 1; })
.catch(function(e) { return e.code });
}
Usage is:
resetpassword(...)
.then(function(num) {
if (num === 1) {
}
else {
}
});
It helps to think how the function would be written in synchronous code:
resetpassword: function(email, oldPassword, newPassword) {
try {
auth.$changePassword(email, oldPassword, newPassword)
return 1;
}
catch (e) {
return e.code;
}
}
A promise is a function that does not execute immediately. Instead, you are 'promising' to respond to the status of a deferred object whenever it has completed it's processing. Essentially, you are creating an execution chain that simulates multiple threads. As soon as a deferred object is complete, it returns a status code, the calling function is able to respond to the status code, and return it's own status code if necessary, etc.
var deferred = $q.defer();
Here you are creating a deferred object, to hold the callbacks which will be executed later.
var promise = auth.$changePassword(email, oldPassword, newPassword);
This is the actual function, but you are not calling it here. You are making a variable to reference it, so that you can monitor it.
var rValue = promise.then(function(){
The .then() is the function that will be executed if the promise is successful. Here you are inlining a function that will execute upon the success of the promise's work.
}, function(error){
And then chaining the error function, which is to be executed if the promise fails. You are not actually doing anything here other than returning the error code, but you could do something to respond to the failure yourself.
deferred.resolve(promise);
This is the point where you are actually telling your deferred object to execute the code, wait for a result, then execute either the success or the failure code as appropriate. The promise is added to the execution chain, but your code is now free to continue without waiting and hanging up. As soon as the promise is finished and a success or failure is processed, one of your callback functions will process.
console.log(rValue);
Your function is not returning the actual values here because you don't want your function to stop and wait for a response before continuing, since this would block the program until the function completes.
return rValue;
You are returning your promise to the caller. Your function is a promise which is calling a promise which might be calling another promise, etc. if your function was not a promise, then even though the code you are calling is not blocking, your code would become a block as you wait for the execution to complete. By chaining promises together, you are able to define how you should respond without responding at exactly that moment. You are not returning the result directly, but instead 'promising' to return a success or fail, in this case passing through the success or fail from the function you are calling.
You use deferred promises when you are going to perform a task that will take an unknown amount of time, or you don't care when it gets done - an Asynchronous task. All you care about is getting a result back whenever the code you called tells you that the task is completed.
What you should be doing in your function is returning a promise on the deferred that you are creating. Your code that calls resetpassword should then wait for this promise to be resolved.
function (email, oldPassword, newPassword) {
var deferred = $q.defer(); //You are doing this to tell your calling code when the $changepassword is done. You could also return the promise directly from angularfire if you like but of course the return values would be different.
var promise = auth.$changePassword(email, oldPassword, newPassword); //$changepassword in angularfire returns a promise
console.log(deferred); //Object with resolve, reject, notify and promise attrs
promise.then(function(){
//successcallback
deferred.resolve(1); //if changepassword succeeds it goes here as expected
}, function(error){
//errorcallback
deferred.reject(0); //if changepassword fails (wrong oldpass for example) it goes here, also works
}
);
// Quite right, you should not do this.
console.log(rValue); //This outputs another promise? Why not 1 or error.code, how the am I ever going to get these values??
return deferred.promise; // Return the promise to your controller
}
What you have done is mixed promises. As you put in the code comments,
$changepassword in angularfire returns a promise
Basically, var deferred = $q.defer() creates a new promise using deferred.promise, but you are already creating a promise with $changePassword. If I understand what you are trying to do correctly, you would simply need to do this.
resetpassword: function (email, oldPassword, newPassword) {
auth.$changePassword(email, oldPassword, newPassword).then(function(result) {
console.log(result); // success here
}, function(err){
console.log(err); // error here
});
}
EDIT:
Upon Robins comment, if you want to deal with the result in a controller, rather than call the then() function inside the service, return the promise itself to the controller. Here is an example:
Service function:
resetpassword: function (email, oldPassword, newPassword) {
// return the promise to the caller
return auth.$changePassword(email, oldPassword, newPassword);
}
Controller function:
.controller('MyCtrl', ['$scope', 'myService', function($scope, myService){
// get the promise from the service
myService.resetPassword($scope.email, $scope.oldPassword, $scope.newPassword).then(function(result){
// use 1 here
}, function(err){
// use err here
});
}]);