Introduction

In part 1 of this post, I spent a lot of time looking at the theory of promises and deferreds: what promises are and how they behave. Now it’s time to actually explore some ways to use promises and deferreds in JavaScript and dive into some best practices. I’ll start with some basic uses and examples of promises and then will dive into a number of specifics pertaining to using promises in jQuery. While the exact specifics of the API may vary from your chosen library, there is enough overlap in concepts to illustrate the use of promises.

Note: The code samples will use jQuery in spite of jQuery’s deviations from the Promise/A proposal, which is particularly noticeable in the case of error handling and when working with other promise libraries. Since part 1 discussed and included links to in-depth articles on the topic, I will refrain from saying anything else about it in this post. The use of jQuery is still wide-spread and their implementation serves as an introduction for many to the use of promises both of which lead me to believe that it is valuable to have an understanding of working with promises in jQuery.

Sequencing Patterns

A deferred is an object representing work that is not yet done and a promise is an object representing a value that is not yet known. In other words, promises / deferreds allow us to represent ‘simple’ tasks and can be easily combined to represent complex tasks and their flows, allowing for fine-grained control over sequencing. This means we can write asynchronous JavaScript parallel to how we write synchronous code. Additionally, promises make it relatively simple to abstract small pieces of functionality shared across multiple asynchronous tasks — consider as an example loading animations, progress animations etc.

Let’s begin with a global view of three common sequencing patterns that promises make possible: stacked, parallel and sequential.

Stacked: bind multiple handlers anywhere in the application to the same promise event.

These patterns can be combined or used separately building up complex tasks and workflows.

Common Use Cases

Many examples of promise use cases pertain to Ajax requests and UI animations. In fact jQuery even returns promises by default from Ajax requests. This makes sense given that promises are ideal for asynchronous tasks whose completion needs to be handled in a unique way. However, that doesn’t mean that the use of promises should be limited to these use case. In fact, promises tend to be a tool worth considering anytime you might otherwise reach for a callback. That said, let’s have a look at some ways we can use promises.

Ajax

Examples of using promises with Ajax requests can be found throughout this post, so I will skip an example here.

Upon executing this, the console output is what I would have expected had I not used promises.

1 started
2 started
2 ended
1 ended

The jQuery API says that .then() is chainable and returns a new promise, so my expectation was that whatever I wrapped in .then() and chained together would occur sequentially and wait for any tasks to finish before moving to the next. Clearly that’s not what happened. Why not?

How does .then() actually work?

Looking in the jQuery source code, we find that:

.then() always returns a new promise

.then() must be passed a function

If .then() is not passed a function:

the new promise will have the same behaviour as the original promise ( which means it is immediately resolved/rejected ),

You probably already see ( if you didn’t see it right away ) why my version didn’t work.
I didn’t explicitly return a promise from .then() so the new promise created by .then() had the same values as the promise it was chained to.

Avoiding the descent into callback hell

We know we need to pass .then() a function for it to be able to do it’s job and we know we need to return a promise from .then(). So we could do the following:

This works. Unfortunately, it’s starting the decent back into callback hell which is one of the things that promises are supposed to help us avoid. Luckily, there are a number of ways to handle this without decending into the territory of deeply nested functions. How we choose to solve it, is of course dependent on our particular situation.

This version admittedly reads very nicely but has the disadvantage of only one named promise which doesn’t really give us the fine-grained control over each step in the process that is desirable in many situations.

Unwinding promises and their handlers

Assuming we want to avoid deeply nested functions and that we should name our promises to give us access to each step of the process, here is a final version:

The advantage gained by this version is that we now have 3 steps which we can clearly refer to giving the advantage of being able to ask each promise for it’s state to send notifications of progress, or later manage our sequencing as needed without having to re-write the code.

Context and passing data

In the Ajax example earlier, we saw that we can pass a value to .resolve() and .fail(). If a promise is resolved with a value, it returns that value as itself.

Best Practices

I’ve attempted to illustrate some best practices along the way but for the sake of clarity, allow me to recap them under one heading. Quite frankly most of these amount to applying other best-practices when using promises: in particular: DRY and the Single Responsibility Principle. In

name your promises

var step2 = step1.then()

separate handler functions from the promise logic by calling a named function from .then() and separate functionality into reusable bits

when it’s logical, return a promise instead of a deferred so that nobody else can inadvertantly resolve/reject the promise

step2.then(function() {
// we don't want to give resolution / rejection powers
// to the wrong parties, so we just return the promise.
return deferred.promise();
});

don’t descend into nested callback hell or nested promise hell

By following these best practices, we can reap the most benefit from promises. We can craft decoupled applications with readable code, gain fine-gained control over asynchronous event sequencing, handle values that don’t exist yet as if they do and operations that haven’t completed yet as if they have.

jQuery Reference

I’d like to wrap-up with an overview of the jQuery API since my code examples have focused on jQuery’s implementation of promises. If you’re using a different implementation of promises, you may want to skip to the end.

Returns a new promise based on the completion of multiple promises. If any promise is rejected, .when() is rejected and if all promises are resolved, it is resolved. It is noteworthy that a non-promise can be passed to .when() and it will be treated as a resolved promise. Also of note is that it will return a single value only if all other promises resolve to a single value or an array, otherwise it resolves to an array.

deferred.resolve(optionalArgs) or deferred.reject(optionalArgs)

Resolve or reject the deferred object and call handler functions ( .done(), .fail(), .always(), .then() ) with any supplied arguments and pass their context to the handler they call.

$('body').on('button', 'click', function() {
// Can be passed a value which will be given to handlers
deferred.resolve();
});

deferred.promise()

Returns the promise object of the deferred. If passed a target, .promise() will attach promise methods to the target instead of creating a new object.

deferred.state()

Useful for debugging and querying the state the deferred object is in. Returns: pending, resolved or rejected.

deferred.always()

Functions or an array of functions called regardless of reject and failure.deferred.done()

Functions or array of functions called upon resolution of the deferred object.

deferred.fail()

Functions or array of functions called upon rejection of the deferred object.

$.ajax()Performs an Ajax request and returns a promise.

Conclusion

Managing asynchronous JavaScript and writing decoupled applications can be challenging. I hope by now you have a better understanding of what promises are, how you can use them and how to avoid some of the common pitfalls. There’s still a lot of ground I haven’t covered in these two posts and for that I refer you to your libraries docs and the resources mentioned at the end of both posts. Of course, if you have questions or feedback feel free to get in touch on app.net or twitter!