/ javascript

Promises: Why, How and Wow!

Promises are possibly the best addition ES6 brought upon us, due to its role as the foundation of async/await. Here's a simple Why, How and Wow explanation of the feature.

Why

In the old days the main pattern to write asynchronous code was events:

const xhr = new XMLHttpRequest();
xhr.open('GET', '/data.json');
xhr.addEventListener('load', handleResult);
xhr.send();

The second parameter to addEventListener is a function and as it happened many developers used inline functions there. A second pattern was callbacks, such as this call to setTimeout:

setTimeout(function() {
    panel.textContent = 'Ouch!';
}, 1000);

Both addEventListener and setTimeout expect the calling code to know it's calling async functionality. Using events it's not always obvious that xhr.send() expects you to call xhr.addEventListener() to be able to read the response, or that setTimeout take the callback as the first argument (and not for example the second).

Promises let invoked functions declare HOW they should be used. A promise has just two ways to go: success or fail, and it provides a unified interface to calling code.

Using promises both pieces of the code above, and many others, could be written in the exact same way. This provides the basis for language constructs for working with promises (async/await), but we're getting ahead of ourselves. Let's move on to how.

How

A promise is an object that takes a function in its constructor, and that function takes 2 callback arguments. Traditionally they're named resolve and reject:

const promise = new Promise(function(resolve, reject) {
  // promise code goes here
});

The promise should call resolve() when the thing it promised happens, or reject() when it finds out the thing it promised won't happen. Promises are usually returned from functions and those functions are called async functions.

Take for example setTimeout we saw earlier:

function sleep(ms) {
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, ms);
    });
}

A promise object keeps a callback that tells it what to do when the promise is solved. We supply that callback by calling then on the promise object (a bit like addEventListener used to work on objects that emitted events).

So to use our sleep function we'll write:

sleep(10).then(doSomething);

That wasn't so world changing so far. And it's good, because promises are just a unified way to look at async code. But already they provide some interesting results such as this recursive sleep and modify codepen:

See the Pen Promises 1 by Ynon Perek (@ynonp) on CodePen.

Wow

But the real wow of promises comes from language constructs that were built around it: async and await.

An async function is automatically split to multiple parts: Each part ends with a promise and the next part starts with the result of the previous promise.

When a promise fails an exception is thrown and we can catch it in a try/catch block.

So the same code from above can now be written as follows:

async function sleepAndModify() {  
  while (true) {
    panel.textContent = texts[idx % texts.length];
    idx += 1;
    await sleep(1000);
  }
}

Or the same logic applied to fetch, which is a promise based communication API:

async function fetchAndShow() {
    try {
        const res = await fetch('/data.json');
        const data = await res.json();
        panel.textContent = data.text;
    } catch (error) {
        panel.textContent = 'Something went wrong';
    }
}

And thus the holy grail of AJAX: synchronised syntax with async semantics.