Understanding JavaScript Promises


A promise represents the eventual result of an asynchronous operation. It is a placeholder into which the successful result value or reason for failure will materialize.

Why Use Promises?

Promises provide a simpler alternative for executing, composing, and managing asynchronous operations when compared to traditional callback-based approaches. They also allow you to handle asynchronous errors using approaches that are similar to synchronous try/catch.

Promise States

A promise can be in one of 3 states:

  • Pending – the promise’s outcome hasn’t yet been determined, because the asynchronous operation that will produce its result hasn’t completed yet.
  • Fulfilled – the asynchronous operation has completed, and the promise has a value.
  • Rejected – the asynchronous operation failed, and the promise will never be fulfilled. In the rejected state, a promise has a reason that indicates why the operation failed.

When a promise is pending, it can transition to the fulfilled or rejected state. Once a promise is fulfilled or rejected, however, it will never transition to any other state, and its value or failure reason will not change.

Promises have been implemented in many languages, and while promise APIs differ from language to language, JavaScript promises have converged to the Promises/A+ proposed standard. EcmaScript 6 is slated to provide promises as a first-class language feature, and they will be based on the Promises/A+ proposal.

Using Promises

The primary API for a promise is its then method, which registers callbacks to receive either the eventual value or the reason why the promise cannot be fulfilled. Here is a simple “hello world” program that synchronously obtains and logs a greeting.

var greeting = sayHello();
console.log(greeting);    // 'hello world’

However, if sayHello is asynchronous and needs to look up the current greeting from a web service, it may return a promise:

var greetingPromise = sayHello();
greetingPromise.then(function (greeting) {
    console.log(greeting);    // 'hello world’
});

The same message is printed to the console, but now other code can continue while the greeting is being fetched.

As mentioned above, a promise can also represent a failure. If the network goes down and the greeting can’t be fetched from the web service, you can register to handle the failure using the second argument to the promise’s then method:

var greetingPromise = sayHello();
greetingPromise.then(function (greeting) {
    console.log(greeting);    // 'hello world’
}, function (error) {
    console.error('uh oh: ', error);   // 'uh oh: something bad happened’
});

If sayHello succeeds, the greeting will be logged, but if it fails, then the reason, i.e. error, will be logged using console.error.

Transforming Future Values

One powerful aspect of promises is allowing you to transform future values by returning a new value from callback function passed to then. For example:

var greetingPromise = sayHello();
greetingPromise.then(function (greeting) {
    return greeting + '!!!!';
}).then(function (greeting) {
    console.log(greeting);    // 'hello world!!!!’
});

Sequencing Asynchronous Operations

A function passed to then can also return another promise. This allows asynchronous operations to be chained together, so that they are guaranteed to happen in the correct order. For example, if addExclamation is asynchronous (possibly needing to access another web service) and returns a promise for the new greeting:

var greetingPromise = sayHello();
greetingPromise.then(function (greeting) {
    return addExclamation(greeting); // addExclamation returns a promise
}).then(function (greeting) {
    console.log(greeting);    // 'hello world!!!!’
});

This can be written more simply as:

var greetingPromise = sayHello();
greetingPromise
    .then(addExclamation)
    .then(function (greeting) {
        console.log(greeting);    // 'hello world!!!!’
    });

Handling Errors

What if an error occurs while performing an asynchronous operation? For example, what if sayHello, or addExclamation fails? In synchronous code, you can use try/catch, and rely on exception propagation to handle errors in one spot. Here is a synchronous version of the previous example that includes try/catch error handling. If an error occurs in either sayHello or addExclamation (or console.log for that matter), the catch block will be executed:

var greeting;
try {
    greeting = sayHello();
    greeting = addExclamation(greeting);
    console.log(greeting);    // 'hello world!!!!’
} catch(error) {
    console.error('uh oh: ', error);   // 'uh oh: something bad happened’
}

When dealing with asynchronous operations, synchronous try/catch can no longer be used. However, promises allow handling asynchronous errors in a very similar way. This allows you not only to write asynchronous code that is similar in style to synchronous code, but also to reason about asynchronous control flow and error handling similarly to synchronous code.

Here is an asynchronous version that handles errors in the same way:

var greetingPromise = sayHello();
greetingPromise
    .then(addExclamation)
    .then(function (greeting) {
        console.log(greeting);    // 'hello world!!!!’
    }, function(error) {
        console.error('uh oh: ', error);   // 'uh oh: something bad happened’
    });

Notice that just like the synchronous example, you can use a single error handling block, in this case passed as the second parameter to the final call to then.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s