Wilsonhut

Deal with it or don't

Tag Archives: promise

$.gasp is published!

jQuery Gasp is published. It’s almost as if they let just anyone on the internet. Am I right?

$.defer renamed to $.whereas, and published

Only because the name $.defer was already taken.

jQuery Whereas

A promisified, progress revealing, cancellable setTimeout

$.defer for advanced promise users – progress, only when you need it!

EDIT: $.defer was renamed to $.whereas

[If you’re not familiar with $.defer, you can go read about it, or just go look at the code.]

Let’s say you have an long-running script inside a function, and you only want to do that work once, even though the function might get called more than once. That function might look like this:

function initializeTheDifficultStuff(){
 if ($(".importantStuff").data("isInitialized")){
   return;
 }
 // Do the difficult stuff

  $(".importantStuff").data("isInitialized", true);
}

The difficult stuff might be some ajax or some other long-running, dom-manipulating javascript.

Now, lets say you decide to make that use the $.Deferred() / promise jQuery feature. Modifying the code above, you could do something like this:

function initializeTheDifficultStuff(){
  if ($(".importantStuff").data("isInitialized")){
    return $.when(); // $.when() returns a resolved promise.
  }
  var d = $.Deferred();
  // Do the difficult stuff
  $(".importantStuff").data("isInitialized", true);
  d.resolve();  
  return d.promise();
}

Now that doesn’t get you much, except the ability to use the .then/.done methods when you call initializeTheDifficultStuff(), since the ‘difficult stuff’ is not asyncronous. If the ‘difficult stuff’ was an ajax call, and you put the d.resolve() in its callback, then you’d have something.

I discussed all of this in a previous post, but now I want to take it a step further…

What if you want to use the progress callback to show a ‘please wait…’ message?

What if you want to use the above method like this? (Here, I’m using the $.blockUI plug-in)

initializeTheDifficultStuff()
  .progress(function(){
    $.blockUI();
  })
  .done(function(){
    //.. show something, do something else.
    $.unblockUI();
  });

The reason you might WANT to do that is so that you could keep the $.blockUI call from happening if the “isInitialized” is already set. That’s what you want, since it’s not going to have to do the hard work in that case, and the $.blockUI call starts a fade-in which would be unnecessary.

Now that gets more difficult with the above implementation of initializeTheDifficultStuff, because, unlike the done and then, events, the progress is never called on a pre-resolved promise. So what can you do? You can return the d.promise, like above, but just before that, put a call to d.notify and d.resolve in a setTimeout. That would start to look ugly real fast. I’m not even going to show that.

$.defer handles this all for you.

You can use the initializeTheDifficultStuff() with the progress and done events, and have clean looking code by using $.defer, like so:

function initializeTheDifficultStuff(){
  if ($(".importantStuff").data("isInitialized")){
    return $.when(); // $.when() returns a resolved promise.
  }

  $(".importantStuff").data("isInitialized", true);
  return $.defer()
       .then(function(){
         // Do the difficult stuff
       });
}

The $.when() returns a resolved promise, so your progress event handler doesn’t get called!
$.defer returns an un-resolved promise. And in this example, it would wait the minimum number of milliseconds, then do the magic. Here’s what it will do for you…
It will first cause your progress event handler to get called. Then it runs the ‘difficult stuff’ for you. Then it is resolved so that your done event handler gets called.

It’s all so pretty and neat, and you get the progress event only when you need it!

Go get the $.defer plug-in from github today!

Introducing… jQuery Gasp – Give a little breathing room to your browser-intensive jQuery

Have you ever done something like…

$("td").someBigNastyPlugin();

…and it was just too much processing for your browser? (I’m looking at you, IE)

It would be nice if your browser had a little bit of time to catch its breath while it’s trying to do all that processing.

jQuery.gasp to the rescue!

Include my gasp plug-in, and replace that code above with the following, and you’ll have a happy browser.

$("td").gasp(function(){
  $(this).someBigNastyPlugin();
});

Essentially, it’s like jquery’s .each function, but it puts a setTimeout around each one, and chains them together, one after the other. If you saw my defer plug-in, this will make more sense.

Now this is asyncronous, but it returns a promise, so you have to treat it accordingly:

$("td").gasp(function(){
  $(this).someBigNastyPlugin();
})
.done(function(){
  console.log("This will run when it's complete");
});
console.log("This will run before the gasping is finished");

The code for this plug-in is below, but the code below requires my jquery.defer plugin.

The jquery.gasp plug-in (on github) includes the jquery.defer code. Just seemed easier (for you) that way.

In later posts, I’ll show you some other cool things you can do with this other than give your browser a little relief. (Spoiler: Remember that $(…) can take things other than a selector string as an argument.)

;(function ($) {
    var defaults = {
		breathTime: 0,
		wait: 0
	};
	$.fn.gasp = function (func, options, promise) {
		options = $.extend({}, defaults, options);
		promise = promise || $.when();
		var isFirst = true;
		this.each(function () {
			var self = this;
			promise = promise.then(function () {
				var p = $.defer(function (el) {
					func.call(el);
				}, (isFirst? options.wait : options.breathTime), self);
				isFirst = false;
				return p;
			});
		});
		return promise;
	};
})(jQuery);

jquery-defer (a jQuery Deferred-ified/promisified setTimeout) is on github

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.