Wrangle Async Tasks With JQuery Promises

Wrangle Async Tasks With JQuery Promises

Tutorial Details
  • Topic: jQuery Deferreds
  • Difficulty: Moderate

Promises are an exciting jQuery feature that make it a breeze to manage async events. They allow you to write clearer, shorter callbacks and keep high-level application logic separate from low-level behaviors.

Once you understand Promises, you’ll want to use them for everything from AJAX calls to UI flow. That’s a promise!


Understanding Promises

Once a Promise is resolved or rejected, it’ll remain in that state forever.

A Promise is an object that represents a one-time event, typically the outcome of an async task like an AJAX call. At first, a Promise is in a pending state. Eventually, it’s either resolved (meaning the task is done) or rejected (if the task failed). Once a Promise is resolved or rejected, it’ll remain in that state forever, and its callbacks will never fire again.

You can attach callbacks to the Promise, which will fire when the Promise is resolved or rejected. And you can add more callbacks whenever you want – even after the Promise has been resolved/rejected! (In that case, they’ll fire immediately.)

Plus, you can combine Promises logically into new Promises. That makes it trivially easy to write code that says, “When all of these things have happened, do this other thing.”

And that’s all you need to know about Promises in the abstract. There are several JavaScript implementations to choose from. The two most notable are Kris Kowal’s q, based on the CommonJS Promises/A spec, and jQuery Promises (added in jQuery 1.5). Because of jQuery’s ubiquity, we”ll use its implementation in this tutorial.


Making Promises With $.Deferred

Every jQuery Promise begins with a Deferred. A Deferred is just a Promise with methods that allow its owner to resolve or reject it. All other Promises are “read-only” copies of a Deferred; we’ll talk about those in the next section. To create a Deferred, use the $.Deferred() constructor:

A Deferred is just a Promise with methods that allow its owner to resolve or reject it.

var deferred = new $.Deferred();

deferred.state();  // "pending"
deferred.resolve();
deferred.state();  // "resolved"
deferred.reject(); // no effect, because the Promise was already resolved

(Version note: state() was added in jQuery 1.7. In 1.5/1.6, use isRejected() and isResolved().)

We can get a “pure” Promise by calling a Deferred’s promise() method. The result is identical to the Deferred, except that the resolve() and reject() methods are missing.

var deferred = new $.Deferred();
var promise = deferred.promise();

promise.state();  // "pending"
deferred.reject();
promise.state();  // "rejected"

The promise() method exists purely for encapsulation: If you return a Deferred from a function, it might be resolved or rejected by the caller. But if you only return the pure Promise corresponding to that Deferred, the caller can only read its state and attach callbacks. jQuery itself takes this approach, returning pure Promises from its AJAX methods:

var gettingProducts = $.get("/products");

gettingProducts.state();  // "pending"
gettingProducts.resolve;  // undefined

Using the -ing tense in the name of a Promise makes it clear that it represents a process.


Modeling a UI Flow With Promises

Once you have a Promise, you can attach as many callbacks as you like using the done(), fail(), and always() methods:

promise.done(function() {
  console.log("This will run if this Promise is resolved.");
});

promise.fail(function() {
  console.log("This will run if this Promise is rejected.");
});

promise.always(function() {
  console.log("And this will run either way.");
});

Version Note: always() was referred to as complete() before jQuery 1.6.

There’s also a shorthand for attaching all of these types of callbacks at once, then():

promise.then(doneCallback, failCallback, alwaysCallback);

Callbacks are guaranteed to run in the order they were attached in.

One great use case for Promises is representing a series of potential actions by the user. Let’s take a basic AJAX form, for example. We want to ensure that the form can only be submitted once, and that the user receives some acknowledgement when they submit the form. Furthermore, we want to keep the code describing the application’s behavior separate from the code that touches the page’s markup. This will make unit testing much easier, and minimize the amount of code that needs to be changed if we modify our page layout.

// Application logic
var submittingFeedback = new $.Deferred();

submittingFeedback.done(function(input) {
  $.post("/feedback", input);
});

// DOM interaction
$("#feedback").submit(function() {
  submittingFeedback.resolve($("textarea", this).val());

  return false;  // prevent default form behavior
});
submittingFeedback.done(function() {
  $("#container").append("<p>Thank you for your feedback!</p>");
});

(We’re taking advantage of the fact that arguments passed to resolve()/reject() are forwarded verbatim to each callback.)


Borrowing Promises From the Future

pipe() returns a new Promise that will mimic any Promise returned from one of the pipe() callbacks.

Our feedback form code looks good, but there’s room for improvement in the interaction. Rather than optimistically assuming that our POST call will succeed, we should first indicate that the form has been sent (with an AJAX spinner, say), then tell the user whether the submission succeeded or failed when the server responds.

We can do this by attaching callbacks to the Promise returned by $.post. But therein lies a challenge: We need to manipulate the DOM from those callbacks, and we’ve vowed to keep our DOM-touching code out of our application logic code. How can we do that, when the POST Promise is created within an application logic callback?

A solution is to “forward” the resolve/reject events from the POST Promise to a Promise that lives in the outer scope. But how do we do that without several lines of bland boilerplate (promise1.done(promise2.resolve);…)? Thankfully, jQuery provides a method for exactly this purpose: pipe().

pipe() has the same interface as then() (done() callback, reject() callback, always() callback; each callback is optional), but with one crucial difference: While then() simply returns the Promise it’s attached to (for chaining), pipe() returns a new Promise that will mimic any Promise returned from one of the pipe() callbacks. In short, pipe() is a window into the future, allowing us to attach behaviors to a Promise that doesn’t even exist yet.

Here’s our new and improved form code, with our POST Promise piped to a Promise called savingFeedback:

// Application logic
var submittingFeedback = new $.Deferred();
var savingFeedback = submittingFeedback.pipe(function(input) {
  return $.post("/feedback", input);
});

// DOM interaction
$("#feedback").submit(function() {
  submittingFeedback.resolve($("textarea", this).val());

  return false;  // prevent default form behavior
});

submittingFeedback.done(function() {
  $("#container").append("<div class='spinner'>");
});

savingFeedback.then(function() {
  $("#container").append("<p>Thank you for your feedback!</p>");
}, function() {
  $("#container").append("<p>There was an error contacting the server.</p>");
}, function() {
  $("#container").remove(".spinner");
});

Finding the Intersection of Promises

Part of the genius of Promises is their binary nature. Because they have only two eventual states, they can be combined like booleans (albeit booleans whose values may not yet be known).

The Promise equivalent of the logical intersection (AND) is given by $.when(). Given a list of Promises, when() returns a new Promise that obeys these rules:

  1. When all of the given Promises are resolved, the new Promise is resolved.
  2. When any of the given Promises is rejected, the new Promise is rejected.

Any time you’re waiting for multiple unordered events to occur, you should consider using when().

Simultaneous AJAX calls are an obvious use case:

$("#container").append("<div class='spinner'>");
$.when($.get("/encryptedData"), $.get("/encryptionKey")).then(function() {
  // both AJAX calls have succeeded
}, function() {
  // one of the AJAX calls has failed
}, function() {
  $("#container").remove(".spinner");
});

Another use case is allowing the user to request a resource that may or may not have already be available. For example, suppose we have a chat widget that we’re loading with YepNope (see Easy Script Loading with yepnope.js)

var loadingChat = new $.Deferred();
yepnope({
  load: "resources/chat.js",
  complete: loadingChat.resolve
});

var launchingChat = new $.Deferred();
$("#launchChat").click(launchingChat.resolve);
launchingChat.done(function() {
  $("#chatContainer").append("<div class='spinner'>");
});

$.when(loadingChat, launchingChat).done(function() {
  $("#chatContainer").remove(".spinner");
  // start chat
});

Conclusion

Promises have proven themselves to be an indispensable tool in the ongoing fight against async spaghetti code. By providing a binary representation of individual tasks, they clarify application logic and cut down on state-tracking boilerplate.

If you’d like to know more about Promises and other tools for preserving your sanity in an ever more asynchronous world, check out my upcoming eBook: Async JavaScript: Recipes for Event-Driven Code (due out in March).

Tags: jQuery
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://cv.zerkms.com Ivan Kurnosov

    The comment

    // one of the AJAX calls has failed

    in the `with()` example is not semantically correct, because it states, that literally one call has failed. It would be better if it was:

    // at least one of the AJAX calls has failed

  • http://www.jvsoftware.com Javier Villanueva

    Nice article, I was looking into it by coincidence and didn’t quite understand it until now :)

  • Kai Schaller

    Great tutorial, I didn’t know about Promises until reading this.

    One correction… in the “Making Promises with $.Deferred” section there is this bit of code:

    var deferred = new $.Deferred();
    var promise = deferred.promise();

    promise.state(); // “pending”
    deferred.reject();
    promise.state(); // “resolved”

    Shouldn’t the last comment be “rejected” instead of “resolved”?

  • http://waqaralamgir.co.cc Waqar Alamgir

    JS error at this line

    $(“#container”).append(“”);

    Make it!

    $(“#container”).append(”);

    • http://waqaralamgir.co.cc Waqar Alamgir

      $(“#container”).append(“”);

  • https://twitter.com/#!/mspycom mspy

    Completely agree that when you understand Promises once, you’ll want to use them for everything from AJAX calls to UI flow.

  • Mazz

    Can someone help me with the contact form example.
    What happens if the POST fails? Doesn’t submittingFeedback already have a state of resolved and therefore would give the thank you message?

    Thanks, still trying to wrap my head around these. :)

    • Mazz

      I just started reading ahead…I guess I should have read the whole article before posting.
      Ignore my previous comment for the moment.

  • http://www.bigbinary.com Neeraj Singh
    • http://www.jeffrey-way.com Jeffrey Way

      Watched that a couple weeks ago. Enjoyed it!

    • http://www.adrianflorescu.info Adrian Florescu

      Great screencast!

  • http://trevorburnham.com Trevor Burnham
    Author

    Thanks to Kai and Waqar for spotting those typos. They’ll be corrected shortly.

    As to Ivan’s comment: Actually, I’d say that “one of the AJAX calls has failed” is more correct, because the Promise generated by $.when is *immediately* rejected when any one of the given Promises is rejected. Since the Promises in this case are independent async tasks, the one that hasn’t been rejected must either be pending or resolved. So, while the other AJAX call may fail too, the fail callbacks on the Promise returned by $.when will always fire before the fail callbacks on the second failing AJAX call.

  • Mazz

    I’ll playing with your contact form and I have a dumb question.
    How can I make it so the form will be able to be submitted again?
    For example if I don’t like what they submitted, I would like to be able to do some server side validation and return a message and then they’d be able to submit again…but I don’t know how to allow this.

    Thanks.

    • http://trevorburnham.com Trevor Burnham
      Author

      @Mazz Just take the code that generated the Deferred and attached the handlers, wrap it in a function, then run that function whenever you enable the form.

  • http://www.GridLinked.info Thomas Burleson

    @Trevor,

    Your sample code seemed cluttered and [IMHO] confuses readers with non-nested code and the pipe() feature. The pipe() method is used to intercept or transform… not so often to sequence chain promises.

    I thought I would suggest some alternative implementations of your sample code; here is the Gist with `suggested` alternatives:

    https://gist.github.com/1910025

    Thanks for the article.

    • http://trevorburnham.com Trevor Burnham
      Author

      Thanks for the suggestion, Thomas, but I prefer my version. Not only does it cleanly separate application logic from DOM interaction, it also gives you two Promises that you can expose to any part of your application that might want to respond to either the user submitting the form or the server accepting it.

      I’d also quibble with your characterization of pipe() as being used mainly to transform, not to chain Promises. Both uses are presented in the jQuery docs, and pipe() was added to jQuery 1.6 explicitly to provide the chaining functionality provided by then() under the Promises/A spec. Chaining is a big reason why Promises are so useful, in my opinion.

      • http://colinjack.blogspot.com Colin Jack

        Great article but I did think the pipe bit was a little muddled, in particular I think this description could do with being fleshed out:

        “While then() simply returns the Promise it’s attached to (for chaining), pipe() returns a new Promise that will mimic any Promise returned from one of the pipe() callbacks. In short, pipe() is a window into the future, allowing us to attach behaviors to a Promise that doesn’t even exist yet.”

        Otherwise though great stuff.

      • http://www.facebook.com/sunnyshahmca Sunny Shah

        Explanation of pipe is indeed confusing.

    • http://gregariousdevelopment.com Greg Nicholas

      I do have to agree that this usage of the pipe() command is a bit confusing for someone’s first exposure to it. Separating DOM-interacting and application code is important, but it introduces some distractions in truly understanding the nature of pipe().

      jQuery’s example is a better first demo of chaining with pipe(), (plus the other examples highlight the command’s filtering capabilities): http://api.jquery.com/deferred.pipe/

      That said, for actual real-world code that needs a separation of DOM and application logic, Trevor’s example is definitely the way to go.

  • http://trevorburnham.com Trevor Burnham
    Author

    The book mentioned in this tutorial, Async JavaScript, is now available: http://leanpub.com/asyncjs/

    The book includes an entire chapter on Promises, with plenty of examples of Promises in action.

  • http://www.matiasmancini.com.ar Matias Mancini

    A little correction.
    .then() does not work with always() in 1.72

  • gildur

    Very educational stuff! Helped me to really understand the relationship between Deferred and Promise! Thank you!