Creating A Timer (Timeout) Using jQuery Deferred Objects

Posted October 11, 2011 at 11:01 AM by Ben Nadel

Tags: Javascript / DHTML

When I talk about using jQuery's new Deferred objects in the context of AJAX, people will often ask my why I don't just use the "success" and "error" callbacks in the AJAX configuration hash. To this, I can only say that I find the deferred / promise aspects create more readable and enjoyable code. Along the same lines, I wanted to see if I could create a Deferred wrapper for the native JavaScript timeout functions (setTimeout() and clearTimeout()). While this does add a bit of new functionality, mostly, I just wanted to further explore the potential uses of Deferred objects.


 
 
 

 
  
 
 
 

When thinking about this timeout wrapper, I wanted to try and add a few fun features to the experiment:

  • Allow additional resolution arguments to be provided during timer creation.
  • Allow the context of the deferred callbacks to be attached to a given object.

For the first feature, I had pictured something like this:

  • Timer( delayInMilliseconds [, arg1, arg2, ... argN ] );

In this case, the first argument is the delay (in milliseconds) that the timer will pause before it gets resolved. This constructor function for the timer can also accept N-additional arguments. These additional arguments will then be passed to the success and fail callbacks of the deferred container.

In order to allow for the context of the callbacks to be altered, I thought the easiest approach would be to simply carry-through the context in which the original Timer() constructor function was invoked. So, if you wanted to execute the callbacks in the context of an object, you would use the call() or apply() methods to invoke the Timer() constructor on that object:

  • Timer.call( contextObject, delayInMilliseconds [, arg1, ... argN ] );

In this case, all callback handlers bound to the resultant deferred object will be invoked in the context of the given "contextObject."

Now that we have a sense of the API, let's take a look at some demo code:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Creating A Timer Using jQuery Deferred</title>
  •  
  • <script type="text/javascript" src="./jquery-1.6.4.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // Create and return the constructor function for the Timer.
  • // We'll wrap this in its own execution space.
  • var Timer = (function( $ ){
  •  
  •  
  • // Define the constructor for the timer.
  • function timer( timeout ){
  •  
  • // Create a new Deferred object - this will be
  • // resolved when the timer is finished.
  • var deferred = $.Deferred();
  •  
  • // Lock the deferred object. We are doing this so we
  • // can alter the resultant object.
  • var promise = deferred.promise();
  •  
  • // Define our internal timer - this is what will be
  • // powering the delay.
  • var internalTimer = null;
  •  
  • // Store the context in which this timer was executed.
  • // This way, we can resolve the timer in the same
  • // context.
  • var resolveContext = this;
  •  
  • // Get any additional resolution arguments that may
  • // have been passed into the timer.
  • var resolveArguments = Array.prototype.slice.call(
  • arguments,
  • 1
  • );
  •  
  •  
  • // Add a CLEAR method to the timer. This will stop
  • // the underlying timer and reject the deferred.
  • promise.clear = function(){
  •  
  • // Clear the timer.
  • clearTimeout( internalTimer );
  •  
  • // Reject the deferred. When rejecting, let's use
  • // the given context and arguments.
  • deferred.rejectWith(
  • resolveContext,
  • resolveArguments
  • );
  •  
  • };
  •  
  • // Set the internal timer.
  • internalTimer = setTimeout(
  • function(){
  •  
  • // Once the timer has executed, we'll resolve
  • // the deferred object. When doing so, let's
  • // use the given context and arguments.
  • deferred.resolveWith(
  • resolveContext,
  • resolveArguments
  • );
  •  
  • // Clear the timer (probably not necessary).
  • clearTimeout( internalTimer );
  •  
  • },
  • timeout
  • );
  •  
  • // Return the immutable promise object.
  • return( promise );
  •  
  • };
  •  
  •  
  • // ---------------------------------------------- //
  • // ---------------------------------------------- //
  •  
  •  
  • // Return the timer function.
  • return( timer );
  •  
  •  
  • })( jQuery );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a new timer with one additional arguments.
  • var delay = Timer( 2000, "Timer, baby!" );
  •  
  • // Bind the timer's success and failure callbacks.
  • delay.then(
  • function( message ){
  •  
  • // Log success!
  • console.log( "SUCCESS:", message );
  •  
  • },
  • function( message ){
  •  
  • // Log failure (timer cleared).
  • console.log( "FAIL:", message );
  •  
  • }
  • );
  •  
  • // delay.clear();
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Create a friend.
  • var tricia = {
  • name: "Tricia",
  • hair: "Blonde",
  • age: 35
  • };
  •  
  • // Now, create a timer, but execute it in the contect of
  • // the tricia object.
  • var friendDelay = Timer.call( tricia, 3000 );
  •  
  • // Bind the success and failure callbacks. Notice that the
  • // THIS executiong context has been maintained through the
  • // definition to the callbacks.
  • friendDelay.then(
  • function(){
  •  
  • // Log the success.
  • console.log( "SUCCESS:", this.name );
  •  
  • },
  • function(){
  •  
  • // Log the failure (timer cleared).
  • console.log( "FAIL:", this.name );
  •  
  • }
  • );
  •  
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Left intentionally blank. -->
  • </body>
  • </html>

As you can see, I'm defining the Timer() constructor function and then demonstrating it in two ways - the first shows off the additional invocation arguments, the second shows off the change of context. And, when we run the above code, we get the following console output:

SUCCESS: Timer, baby!
SUCCESS: Tricia

In the first line, the additional argument, "Timer, baby!" was successfully passed-through to the deferred callback. In the second line, the context of the callback was successfully passed-through from the original invocation (which provided access to this.name).

Although I did not demo it in the code, I augmented the deferred promise() object with a clear() method. Once the Timer() is created, you can call clear() on the return value in order to cancel the timeout. This will clear the internal timeout and reject the deferred object. This will result in the invocation of any bound fail() callbacks.

More than anything, this was just a fun exploration of the jQuery Deferred object. In fact, this was the first time that I have ever even used the resolveWith() and rejectWith() methods. I don't know why exactly, but I find it deeply satisfying to be able to bind success and fail callbacks after an object has been created. It just makes my happy!




Reader Comments

Oct 13, 2011 at 9:49 AM // reply »
1 Comments

I think to be extra useful, you should allow the constructor to specify whether the timer going off is a success or a failure. Then I could combine the promise I get from the timer with promises I get from Ajax calls or any other asynchronous operation using $.when() into a single promise that has a timeout duration.

A large part of the power of deferreds is their ability to be combined.


Oct 14, 2011 at 2:32 AM // reply »
1 Comments

There a error in the demo code, the correct way should have been

  • internalTimer = setTimeout( function() ...

so when clearTimeout( internalTimer ) is called the timer would stop, but why is still seems to work fine? I'm guessing that the answer is when
deferred.rejectWith is called, it would prevent the deferred.resolveWith to do anything because just one of the callbacks was fired.


Oct 17, 2011 at 9:37 AM // reply »
10,743 Comments

@John,

Just so I understand, you're saying that it would be nice to flag a fulfilled timer execution as a "reject" outcome, rather than a "resolve" outcome?

@Juan,

Ahhhh! Excellent catch! You are 100% correct. That was a bad mistake; and, you're right - the nature of the Deferred object has hidden the error since the deferred object cannot be finalized more than once.

Great catch!!!


Oct 17, 2011 at 9:40 AM // reply »
10,743 Comments

@Juan,

Thanks again - this has now been fixed. You the man ;)


Oct 20, 2011 at 9:44 AM // reply »
347 Comments

@Ben,

What in the world? Tell me your account got hacked because of this post:

http://www.bennadel.com/blog/2272-Getting-Contact-Photos-And-Social-Media-Information-Using-FullContact-com-And-An-Email-Address.htm

That or you've gone crazy. lol Too much coding?


Oct 20, 2011 at 10:14 AM // reply »
10,743 Comments

@Anna,

Ha ha, that was not me :D Someone was just posting using my email address. I've deleted the comments.


Oct 20, 2011 at 11:35 AM // reply »
347 Comments

Thanks! I'm a bit sensitive about stuff like that, especially if it could potentially affect other people. I wouldn't want your girlfriend coming on here and reading those comments and thinking I was trying to flirt with you or hit on your or anything like that. I'm a little flirty by nature sometimes, but I definitely respect others' relationships.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 21, 2012 at 1:58 AM
Updated: Converting A ColdFusion Query To CSV Using QueryToCSV()
Hi Ben, why do you need to have so many double quotes when adding the field and field name to the row data? ----------------------------------------- <cfset LOCAL.RowData[ LOCAL.ColumnIndex ] = ... read »
AXL
May 21, 2012 at 1:24 AM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Mounir, Open your lower case URL Rewrite rule and add the following condition. Condition input: {REQUEST_URI} Check if input string: Does Not Match the Pattern Pattern: ^/CFFileServlet/_cf_ca ... read »
May 20, 2012 at 4:28 AM
Understanding The Complex And Circular Relationships Between Objects In JavaScript
@Will Vaughn I tried your javascript example but got this error:- foo.print is not a function ... read »
May 19, 2012 at 5:37 AM
A Graphical Explanation Of Javascript Closures In A jQuery Context
Thanks for this article, but I fear you missed an important point. If variables in the outer context change, these changes affect the inner anonymous functions as well. That means: if you change the ... read »
May 18, 2012 at 3:39 PM
Parsing CSV Data With An Input Stream And A Finite State Machine
Can you use file upload button with this? and read live? or does the file have to already be on the server saved? ... read »
May 18, 2012 at 1:06 AM
VIRGO (Aug. 23-Sept. 22): Dead On The Money!
A friend of mine and I were arguing about astrology and she told me that he believes in astrology. She hasn't provided me with any evidence that the belief makes any sense to me. She she been telling ... read »
May 17, 2012 at 11:32 PM
Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server
Very easy the configuration. I read a lot pages and I can't find the solution. I open the administrator and change this Administrator/server settings/Error Handlers/Missing Template Handler and p ... read »
May 17, 2012 at 3:13 PM
LOCAL Variables Scope Conflicts With ColdFusion Query of Queries
I never cease to be amazed that almost EVERY random CF issue I come across lands me on your site. Thank you for documenting your findings for the world. ... read »