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
});
}]);
Related
I am starting to learn about promises in Javascript and I am still not getting my head around it. The code below is mostly real code. I have placed several debugger statements so the program stops and I can understand how the flow works and inspect some variables. I have read some blog posts about promises and I still can't understand everything. This is from an app which uses AngularJS and q library.
Few questions:
1- What does deferred.Resolve() do exactly? What is it doing with response.data? When I inspected the 'deferred' object and its 'promise' object, I couldn't see any trace for response.data.
2- When I resumed execution after debugger #1, I thought the http post statement would run but execution jumped to the return statement. I guess that's where the promise jumped in and the post will happen in the future?
3- How do I know when the post will happen when the function returns? The caller will get the return promise, what is the caller expected to do with it?
this.GetData = function()
{
var data = blahblah;
var deferred = this.$q.defer();
debugger; //1
this.$http.post(someurl, data,
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
handleErrors: false
})
.then(function(response) {
debugger; //2
(domesomething...)
deferred.resolve(response.data);
},
function(error) {
(logerror...)
deferred.reject(error);
});
debugger; //3
return deferred.promise;
};
It's appropriate to use q.defer() (an explicit creation of a promise) when wrapping callback style code, "promisifying"...
this.timeoutWithAPromise = function(msec) {
let defer = q.defer();
setTimeout(() => defer.resolve(), msec);
return defer.promise;
};
The foregoing says: "Create a promise and return it right away. That promise will be fulfilled when msec has passed.
Question 1: resolve() fulfills the promise, calling whatever function that's been set with then(), passing it whatever parameter was passed to resolve.
Question 2: You're right, the async operation is begun and a promise for it's completion is returned right away.
Question 3a: The post will begin (or the timeout will commence in my e.g.) on another thread as soon as that invocation is made. It will continue concurrently with execution on this calling thread, until it completes, which you understand from Question 1, is done by resolving the promise, invoking it's then().
Question 3b: What is the caller to do with a returned promise? Attach a block to it that you wish to run upon completion, possibly additional async actions. Taking my example...
let self = this;
self.timeoutWithAPromise(1000).then(()=> {
console.log('1000msec have passed');
return self.timeoutWithAPromise(1000);
}).then(()=> {
console.log('now, another 1000msec have passed');
// ...
Rewriting your example, realizing that $http already conforms to promises...
var data = blahblah;
let headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
let config = { headers: headers, handleErrors: false };
let httpPromise = this.$http.post(someurl, data, config).then((response)=> {
console.log('got a response. tell our caller about it');
return response.data;
}).catch((error)=>
console.log('got an error. either handle it here or throw it');
throw error;
});
// this is important: return the httpPromise so callers can chain off of it
return httpPromise;
Now a caller can say:
let promiseToDoEvenMore = this.GetData().then((data)=> {
console.log(data);
return this.GetMoreData(); // etc.
});
return promiseToDoEvenMore; // and so on
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
I have the below function, which checks if the user has entered the right password and username. This functions correctly however the function returns before the success/error functions are fired.
Therefore the function always returns true, what would be the best method to have the function return the right value.
angular.module('services.security', [])
.provider('securityService', function() {
this.$get = function ($q, $location, $rootScope, $window, $injector) {
...
loginCheck: function(username, password) {
var failed = true;
$injector.get('$http').post(AUTH_URL,
{username: username, password: password}, {loginType: "cancel"}
).success(function loginSuccess(data) {
failed = false;
}).error(function loginFailure(data, status) {
failed = true;
});
return failed;
},
You would want to return the promise.
So you can just return your http-call:
return $http("...");
In your code you can then just use this returned promise to check if it was succssful:
loginCheck(u, p).then(function() {
//success
}, function() {
// error
});
You should do some research into JavaScript Promises. That's what you're working with here. The problem is that HTTP calls are (generally) asynchronous, meaning that the code doesn't wait for them to finish before continuing. This is where promises come in.
Instead of returning the true/false value directly, your function should instead return a promise representing the true/false value. You can then attach handler functions to it that essentially say "whenever the HTTP call finishes, pass the result into these functions. If you're using a more recent version of Angular, e.g. 1.4.4+ or 1.5.x (not sure about Angular 2, but probably this will still apply), the $http.post() method returns a promise anyway, so you can return that directly.
The key thing is the .then() method of promises. This takes 2 arguments: the first argument is a success function, the second is a failure function. So you should just rewrite your code like this:
loginCheck: function(username, password) {
var failed = true;
return $injector.get('$http').post(AUTH_URL,
{username: username, password: password}, {loginType: "cancel"}
);
}
// ...
// somwhere else where securityService.loginCheck() is used
securityService.loginCheck(username, password).then(
// success
function(data) {
// handle successful login
},
// failure
function(data, status) {
// handle failed login
}
);
This is how asynchronous code is written these days. Definitely read up on Promises and asynchronous concepts i general, it's super important in modern web development.
An introduction to promises
Promises A+ standard
MDN's documentation on the recent built-in Promise type
$q, Angular's promise service
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.
The following code returns an RSVP promise from each getJSON method:
getJSON('/login')
.then(getJSON('/errors').then(function(users) {
self.user = users;
}))
.then(getJSON('contacts').then(function(contacts) {
self.contacts = contacts;
}))
.then(getJSON('companies').then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
}));
My understanding of promises is obviously wrong, I don't want to provide an error callback for each then and instead I thought that the error would be forwarded on to the next available error callback in one of the subsequent then functions.
In the above code, the getJSON on line 2 will be rejected but it is not forwarded onto the error callback in the last then.
Do I have to provide an error callback for each then. This does not seem any different from callback hell.
Is there any reason you're not making all requests at once?
self.user = getJSON("login");
self.contacts = getJSON("contacts");
self.companies = getJSON("companies");
// not sure what "/errors" does or what you assign it to, add it if you please
//.hash will go through object properties and wait for all promises to resolve
return RSVP.hash(self).then(function(results){
//here, self now contains .user .contacts and .companies
callback(self); //DO NOT do this, instead return the promise
}).catch(function(err){
//this is where all error handling goes
});
Note the return there, it's best to return the promise rather than call a callback, you can .then it from the outside.
I think you're chaining your promises in a wrong way. Your getJSONS are not executing in succession after the previous completes.
When you call the first getJSON (getJSON('/login')), you are not passing two handlers to its then method. You are passing a new getJSON call. This means that, just after the call to getJSON finishes (but before the ajax call ends) you are executing the second getJSON (getJSON('/errors')). You are passing the resulting promise as the first argument of the then method of getJSON('/login'). By doing this, this promise will resolve or reject with the same value as getJSON('/errors'). Okay but...
.then(getJSON('companies').then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
}));
Here you are passing a new promise, the one returned by getJSON(companies) as the first argument of a then method. The promise to which this method belongs, when rejected, will try to call the function passed as the second argument ... but there's none! So, because getJSON('companies') does not reject, you don't receive an error. I've rewritten your chain:
getJSON('/login').then(
function(users) {
self.user = users;
return getJSON('/errors');
}).then(function() {
return getJSON('contacts')
}).then(function(contacts) {
self.contacts = contacts;
return getJSON('companies');
}).then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
});
Now I think it should work fine. After a succesfull resolve of each promise, a function making a new getJSON request will execute, and it will return its promise. If any of them rejects, the rejection will pass through until a then with second argument is found, which happens at the end of the chain. The last function(err) will capture anything that went wrong before that point.
Similar to Benjamin Gruenbaum's, but slightly more concise.
return RSVP.hash({
user: getJSON("login");
contacts: getJSON("contacts");
companies: getJSON("companies");
}}).then(callback).catch(function(err){
//this is where all error handling goes
});