9

Can I use jquery done() on "non-ajax" functions. I get the error Uncaught TypeError: Cannot call method 'done' of undefined when I try to do something like this.

function countThreeSeconds() {
    var counter = 0,
    timer = setInterval(function () {

        if (counter == 3) {
            console.log("All done. That was three seconds.");
            window.clearInterval(timer);

        } else {
          console.log("Not there yet. Counter at: " + counter);
        }
        counter++;
    }, 1000);

}

(function(){
  var timer = countThreeSeconds().done(function(){
     alert("done");
  });

}());

Thanks

JSBIN

1252748
  • 14,597
  • 32
  • 109
  • 229
  • 3
    As you can see, you can't. jQuery has nothing to do with it. You can never call a method on `undefined`. jQuery methods don't just magically appear everywhere *(thank goodness)*. You can call them on specific objects defined in the jQuery library and as described by jQuery's API. –  Apr 18 '13 at 21:30

3 Answers3

15

Make the non-ajax function return a promise object.

function countThreeSeconds() {
    var counter = 0,
    deferred = $.Deferred(),
    timer = setInterval(function () {

        if (counter == 3) {
            console.log("All done. That was three seconds.");
            window.clearInterval(timer);
            deferred.resolve();

        } else {              
            console.log("Not there yet. Counter at: " + counter);
            deferred.notify(counter);
        }
        counter++;
    }, 1000);
    return deferred.promise();

}

(function(){
  var timer = countThreeSeconds().done(function(){
     alert("done");
  }).progress(function(i){
     console.log("in progress...",i);
  });

}());
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • This is really nice. And makes perfect sense. So obvious now that I couldn't expect a normal function to automatically have access to all those deferred methods like jquery ajax. Thanks much! – 1252748 Apr 18 '13 at 21:43
  • `console.log()` accepts an infinite( i think? ) number of arguments, it just allows you to log more than one thing, or label each log so that you know what it is. – Kevin B Apr 18 '13 at 22:02
2

Since you're not returning anything from the function, it's perfectly valid JS behavior. To be able to use done on the function, return jQuery.Deferred object from it.

Something like this:

function countThreeSeconds() {
    var defer = $.Deferred(function() { // do your stuff here });
    return defer.promise();
}
J0HN
  • 26,063
  • 5
  • 54
  • 85
  • Why are you passing a function to `$.Deferred`? That's *not* where your code goes. – gen_Eric Apr 18 '13 at 21:38
  • @RocketHazmat There's nothing wrong with doing it in the function. http://pastebin.com/CWXWcmkS – Kevin B Apr 18 '13 at 21:44
  • @KevinB: I just looked at the docs. Guess you're right, but I've *never* seen it done that way. – gen_Eric Apr 18 '13 at 21:45
  • @KevinB: Whoah! I like that syntax. :-) – gen_Eric Apr 18 '13 at 21:46
  • @RocketHazmat Me too. It's just usually easier to sprinkle it into existing code using the syntax i used in my answer. – Kevin B Apr 18 '13 at 21:47
  • @KevinB: The way in your answer is usually how I do it. Actually, in the tutorial I read, I saw `deferred = new $.Deferred` instead, so I didn't know about passing a function to the constructor. :-) – gen_Eric Apr 18 '13 at 21:48
1

Thomas, with something like this, you could make a Deferred object (and its promise) really work for you.

For example, you could make countThreeSeconds() a raw, more generalized function, and have all progress/done reporting performed in the calling function.

function countSeconds(n) {
    var dfrd = $.Deferred(),
        counter = 0,
        timer = setInterval(function() {
            counter++;
            if (counter < n) { dfrd.notify(counter); }
            else { dfrd.resolve(); }
        }, 1000);
    return {
        promise: dfrd.promise(),
        timer: timer
    };
}

(function() {
    var timerObj = countSeconds(3);
    timerObj.promise.progress(function(counter) {
        console.log("Not there yet. Counter at: " + counter);
    }).done(function() {
        clearInterval(timerObj.timer);
        console.log("All done. That was three seconds.");
        alert("done");
    });
}());

Thus, another function could call countSeconds() with a different value, and handle progress and done situations differently.

For example :

(function() {
    var timerObj = countSeconds(10);
    timerObj.promise.progress(function(counter) {
        $("#message").text("Counter = " + counter);
    }).done(function() {
        clearInterval(timerObj.timer);
        $("#message").text('Complete');
    });
}());
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44