Wilsonhut

Deal with it or don't

Monthly Archives: May 2013

jQuery Deferred-ified window.setTimeout

EDIT: $.defer was renamed to $.whereas

[Code] [jsfiddle sample][on github]

“Too bad you can’t use window.setTimout and have it implement the jQuery Deferred interface.”

We’re all upset about that. So…

“Wait. What? Now you can?!?”

Right. Now you can. I searched the internet and couldn’t find an existing one, to my surprise. I wrote it for you though.

You can skip right to the Code below, skip to a jsfiddle sample, or keep reading for more explanation.

It’s almost as simple as my sample code in my last post explaining the basics of Deferred with the addition of an actual setTimeout… Make a $.Deferred. Call the provided func in a setTimeout, using the provided delay and parameters. After the call to the provided func, call .resolve, and in a catch block, call .reject.

“How do you use it?”

Use it just like setTimeout
setTimeout:

setTimeout(function(){
    console.log("I'm in the future. " + new Date());
}, 2000);

My new $.defer method:

$.defer(function(){
    console.log("I'm in the future. " + new Date());
}, 2000);

It’s the same. You can even pass parameters, just like setTimout. (I didn’t bother implementing the overload that takes a string and eval’s it, because who cares)

“So what’s the big deal?”

  • It works the same way as your beloved setTimeout, only with the $.defer, you can chain callbacks on the end…
    $.defer(function(){
        console.log("I'm in the future. " + new Date());
    }, 2000)
    .done(function(){
        console.log("All done");
    });
    

    and you get all the benefits of the rest of the callbacks – done/fail/then/always, etc.

  • You can use it anywhere that expects a promise – for example, you can use it as a participant in jQuery’s $.when()!
  • Since there are callbacks, you have a way to get the return value of the function that you called.
    $.defer(function(){
        return "no you didn't";
    }, 2000)
    .done(function(x){
        console.log(x); // x is "no you didn't" 
    });
    
  • It’s cancel-able.
    $.defer(function(){
       console.log("Doing work...");
    }, 2000)
    .progress(function(){
        console.log("Sorry to have to cancel this.");
        this.cancel();
    })
    .done(function(){
        console.log("Won't get here.");
    })
    .fail(function(){
        console.log("This will happen");
    });
  • You can use it without the function parameter to return a promise that will resolve in [delay] milliseconds

    return $.defer(100);
    

    The delay is optional too. The default delay of 0 (zero) will be used.

    return $.defer(function(){alert("ASYNC!");});
    

    or

    return $.defer();
    

“You had me at ‘Promise’, now shut up and give me the code”

Here it is. It’s a little longer than the way I explained it above, but that’s just because it has the code for cancelling, and I like for the actually code to look long and complex to make me look smarter.


THE CODE


;(function($) {
	var slice = Array.prototype.slice;
	$.defer = function( /*[func] [, delay] [, funcParameters]*/) {
		var args = arguments,
		    isFunc = $.isFunction(args[0]),
		    func = isFunc ? args[0] : $.noop,
		    delay = (isFunc ? args[1] : args[0]) || 0,
		    funcArgs = isFunc ? slice.call(args, 2) : [],
		    isCancelled = false,
		    cancel = function(reject /* = true */) {
			    clearTimeout(timerId);
			    isCancelled = true;
			    if ((!arguments.length || reject) && deferred.state() === "pending") {
				    deferred.rejectWith(null, funcArgs);
			    }
		    },
		    deferred = $.Deferred(),
		    timerId = setTimeout(function() {
			    deferred.notifyWith(promise, funcArgs);
			    if (isCancelled) {
				    return;
			    }
			    try {
				    var result = func.apply(this, funcArgs);
				    deferred.resolveWith(result, funcArgs);
			    } catch(e) {
				    deferred.rejectWith(e, funcArgs);
			    }
		    }, delay),
		    promise = $.extend(deferred.promise(), {
			    cancel: cancel
		    });
		return promise;
	};
})(jQuery);

The simplest explanation of jQuery Deferred

I spent a while trying to get to the bottom of the question, “What is the jQuery Deferred all about?”

I couldn’t find the simple answer (They all seem to include ajax examples), so I decided that the internet needed a very short post about the basics.

Deferred is all about callbacks

Let’s say you are the developer of some asynchronous method. You need to provide a way to callback to the consuming code so that it can be aware of success and failure. One way to do this is to make use of a $.Deferred().

As the author of the method, you would create a new $.Deferred(), and use that* as the return value of the function.
When the real meat of your code has completed, you call the deferred’s .resolve method, and if the meat of your code fails, you call the deferred’s .reject method.

When you call .resolve or .reject, all of the appropriate callbacks that the consumer set up get called.

As the basics, that’s it. Make a Deferred. Call resolve or reject, then the done or fail callbacks get called.

Here’s an example.

function playMonopoly(){
    // long-running code.
    throw new Error("A player gave up.");
}

function thisMightTakeAWhile(){
    var deferred = $.Deferred();
    try{
        playMonopoly();
        deferred.resolve();
    }catch(e){
        deferred.reject();
    }
    return deferred; // *
}

thisMightTakeAWhile()
.done(function(){
    console.log("Finished? Wow!");
})
.fail(function(){
    console.log("Someone always gives up.");
});

(You can see it in action on this jsfiddle)

That’s a bad way to use it because nothing there is asynchronous, but that’s the simplicity that I felt the internet was missing.

* Really, you should return deferred.promise()

So what is the difference between a Deferred and a Promise?

The promise is just like returning the Deferred, but with the promise, the consuming code doesn’t have access to the .resolve .reject, and other methods that CHANGE the state. Using the promise, the consuming code only has access to the .done, .fail, etc.

Obvious Extension Methods: IEnumerable Lazy DefaultIfEmpty

Is your Aggregate throwing an InvalidOperationException: Sequence contains no elements?
This line of code certainly would:
new string[]{}.Aggregate((ag, s) => ag + ", " + s)

Use this instead:
new string[]{}.DefaultIfEmpty("[NA]").Aggregate((ag, s) => ag + ", " + s)

I was writing some extension methods for things such as Aggregate where the only purpose was to pass in default values. That was not very smart, since you can always use DefaultIfEmpty() and pass your default value there. Really, what I needed was an overload of DefaultIfEmpty that takes a Func to get the default value, in case the default value is expensive (a database look-up, for example)

So here that is:

public static IEnumerable<TSource> DefaultIfEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource> getDefaultValue)
{
	using (var enumerator = source.GetEnumerator())
	{
		if (enumerator.MoveNext())
		{
			do
			{
				yield return enumerator.Current;
			}
			while (enumerator.MoveNext());
		}
		else
			yield return getDefaultValue();
	}
}

Now you can say
new string[]{}.DefaultIfEmpty(() => goGetTheDefaultValue()).Aggregate((ag, s) => ag + ", " + s)

Obvious Extension Method: IEnumerable MaxOrDefault and MinOrDefault

This one is even too obvious to comment on:

public static TResult MaxOrDefault(this IEnumerable source, Func selector, TResult defaultValue)
{
	return source.Select(selector).DefaultIfEmpty(defaultValue).Max();
}

public static TResult MaxOrDefault(this IEnumerable source, Func selector)
{
	return source.Select(selector).DefaultIfEmpty().Max();
}
		
public static TResult MinOrDefault(this IEnumerable source, Func selector, TResult defaultValue)
{
	return source.Select(selector).DefaultIfEmpty(defaultValue).Min();
}

public static TResult MinOrDefault(this IEnumerable source, Func selector)
{
	return source.Select(selector).DefaultIfEmpty().Min();
}

It just makes the code a little more succinct when trying to avoid the InvalidOperationException: Sequence contains no elements

Obvious Extension Methods: IEnumerable AnyAndAll

This might not be intuitive to you (it wasn’t to me)

What does the following line return?

Enumerable.Empty<string>().All(t => t == "I don't think so")

…or maybe this would even be more confusing:

Enumerable.Empty<string>().All(t => t != t)

Well… both of those return true. (See Vacuous Truth)

I didn’t want to have to tack a call to Any() in my code for this situation, so I wrote AnyAndAll (which also performs better since it won’t even begin to enumerate the list twice.

Here it is:

  1. public static bool AnyAndAll<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  2. {
  3.     if (source == null)
  4.         throw new ArgumentNullException(“source”);
  5.     if (predicate == null)
  6.         throw new ArgumentNullException(“predicate”);
  7.     bool hasAny = false;
  8.     foreach (var item in source)
  9.     {
  10.         hasAny = true;
  11.         if (!predicate(item))
  12.             return false;
  13.     }
  14.     return hasAny;
  15. }

You’re welcome.