0

I am really having trouble wrapping my head around the deferred() method inside jquery. I've spent several hours reading the documentation, but I still don't fully understand what I'm doing.

My basic problem is, I have a series of functions (not ajax calls) that I want to run in sequence, but stop all processes if there is an error in any of them.

Here is how far I've gotten (I've stripped out a bunch of unneeded code and just left the basic idea)

//The module  var myModule = (function() {

//Defaults
var vOne;
var VTwo;
var deferred = $.Deferred();

//Private method
var _myPrivateFunction1 = function(userID) {
    if(userID >= 10) {
        //All is good, set var vOne to true and run next function
        vOne = true;
        return deferred.promise();
    } else {
        //User is not allowed, stop everything else and show the error message
        return deferred.reject();
    }
}

var _myPrivateFunction2 = function() {
    if(vOne === true) {
        //Ok we can keep going
        return deferred.promise();
    } else {
        //again stop everything and throw error
        return deferred.reject();
    }
};

var _myPrivateFunction3 = function(element) {
    //...and so on
};


var _errorMsgFunction = function(msg) {
    $.log("There was an error: " + msg);
    return false;
};


//Public method
var myPublicFunction = function(element,call) {
    //element is jquery object passed through user "click" event
    var userID = element.data('id')
    var userAction = element.data('action');

    //Now... i want to fire _myPrivateFunction1, _myPrivateFunction2, _myPrivateFunction3 in sequence and STOP all processes, and run 
    // _errorMsgFunction if there is an error in any of them.
    //This is how far I've gotten...

    _myPrivateFunction1(userID).then(_myPrivateFunction2(userAction), _errorMsgFunction("Error in _myPrivateFunction2")).then(_myPrivateFunction3(element),_errorMsgFunction("Error in _myPrivateFunction3")).fail(_errorMsgFunction("Error in _myPrivateFunction1"));

};

// Public API
return {
    myPublicFunction: myPublicFunction
};
})();

So right now I keep getting "Error in _myPrivateFunction2" (I'm forcing this error for testing purposes), but the other functions after continue to fire...They don't stop. What am I missing here?

Edited Content
  • 187
  • 1
  • 2
  • 10
Amir
  • 4,211
  • 4
  • 23
  • 41
  • Using defered is considered a anti-pattern. See [this issue](http://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). – Henrique Barcelos Aug 25 '15 at 21:27

2 Answers2

2

You cannot share deferred objects. You should create a different promise from a deferred for each function.

Here is some very simple example, using sycnhronus functions for the sake of simplicity, although promises are meant to be used with asynchronous functions:

var func1 = function(arg){
    var dfd = jQuery.Deferred();
    if (arg === 0) {
        dfd.resolve('func1 Ok');
    } else {
        dfd.reject('func1 arg != 0');
    }
    return dfd.promise();
}

var func2 = function(arg){
    var dfd = jQuery.Deferred();
    if (arg === 0) {
        dfd.resolve('func2 Ok');
    } else {
        dfd.reject('func2 arg != 0');
    }
    return dfd.promise();
}

var func3 = function(arg){
    var dfd = jQuery.Deferred();
    if (arg === 0) {
        dfd.resolve('func3 Ok');
    } else {
        dfd.reject('func3 arg != 0');
    }
    return dfd.promise();
}

If the functions does not depend on other to do their processing, we can do it in parallel using jQuery.when

// Parallel processing

jQuery.when(func1(1), func2(0), func3(0)).then(function(res1, res2, res3){
    console.log(res1, res2, res3);
}).fail(function(reason){
    console.error(reason); // will fail with reason func1 arg != 0
});

If it is a sequence processing (as I undertood your problem is), you should do:

// Sequential processing

func1(0).then(function(res1){
    return func2(res1);
}).then(function(res2){
    return func3(res2);
}).then(function(res3){
    // everything ran ok, so do what you have to do...
}).fail(function(reason){
    console.log(reason);
});

The code above will fail with reason:

> func2 arg != 0

If you have mixed parallel and sequential processing to do, then you should mix both approaches.


Disclaimer

As in my example, if func1 or func2 have side effects, they will not be undone within fail() by themselves.

The best practice is to only have side effects when you are absolutely sure that everything went ok, that is, inside the last then() call.

Henrique Barcelos
  • 7,670
  • 1
  • 41
  • 66
  • 1
    +1! Maybe you should add a notice though that in general deferreds/promises should not be used in synchronous functions like those in the example. – Bergi Aug 25 '15 at 22:07
  • I feel like I'm still missing a component to fully grasp this concept. First of all, you are 100% right, my example is rubbish because I don't need defer anything if I'm not using AJAX, because all my functions are running synchronously anyway. But, is there another way of explaining what func1(0).then(function(res1){ return func2(res1); }) does? What exactly is happening here? You run func1, you pass 0 as arg, then the function runs, sends back what? A string? Object? And now what is function(res1)? why is is wrapping return func2(res1)? And lastly, what is the return in this case doing? – Amir Aug 25 '15 at 23:36
  • Rule of thumb: `promise.then` always return a promise. – Henrique Barcelos Aug 25 '15 at 23:40
1

You will need a separate $.deferred() inside each of your functions, because you want to return unique promise for each function.

//Private method
var _myPrivateFunction1 = function(userID) {
    var deferred = $.Deferred();
    if(userID >= 10) {
        //All is good, set var vOne to true and run next function
        vOne = true;
        deferred.resolve();
    } else {
        //User is not allowed, stop everything else and show the error message
        deferred.reject();
    }
    return deferred.promise();
}

Then your public function should work.

lanan
  • 2,742
  • 3
  • 22
  • 29