Question

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
            },
Was it helpful?

Solution

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;
     }
 }

OTHER TIPS

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
    });
}]);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top