Asynchronous JavaScript Programming with Promises
OUR Blog
Frontend developer
Alexander Changli
Frontend developer
FRONTEND
Sep 19 2016

Asynchronous JavaScript Programming with Promises

Introduction

Promises are not something new to Javascript. This programming paradigm is more than 30 years old and it has been used for about 10 years in JavaScript (which means ages in front-end world). Many JavaScript frameworks and libraries support promises today. Nevertheless, a lot of developers do not use promises or ignore advantages of this technology. In this article we will try to remind what the promises at glance are, how they work and what are the best ways to use them.

The problem

JavaScript is WEB browser language (yep, we know about Node.js, but let’s leave it outside the scope of this article). It means that JavaScript interacts with user interface. One of the most important things for frontend app is to be responsive. Modern end-users are choosy and nervous guys who hate freezed UI, today nobody can afford stopping execution of main thread during synchronous processing of long-running tasks. The most obvious and natural example of such continuous task is interaction between frontend and backend apps. It’s not acceptable to block UI and make user wait till client-server communication is finished. That’s why we have such a nice technique as AJAX (asynchronous JavaScript + XML). With AJAX the client app can send data to server asynchronously and continue entertaining user untill server response is retrieved. But since AJAX request works in the background it somehow needs to notify main script that data is ready and then convey this data. Originally this is done with event handling callback functions. Here is a simple example which illustrates this approach (it retrieves Star Wars most vulgar character from a REST endpoint):

Rude Droid Codepen Example

var mostVulgarStarWarsCharacter = 'https://swapi.co/api/people/3/';

var xhr = new XMLHttpRequest();
xhr.open('GET', mostVulgarStarWarsCharacter, true);
xhr.send();
xhr.onreadystatechange = function () {
    if (xhr.readyState != 4) {
        return;
    }
    if (xhr.status === 200) {
        var response = JSON.parse(xhr.responseText);
        console.log('BEEP, BOOP, BEEP! My name is ' + response.name + '. I\'m very rude droid. They beeped out every singe word I said.');
    } else {
        var error = xhr.statusText || 'The reason is mysterious. Call Yoda!';
        console.error('Oh bother! The Galaxy is in Danger again! ' + error);
    }
};

Ahhh, what a neat, handsome, built with bleeding edge technologies example! But let’s get a little closer to the real world. Imagine that your customer has some additional requirements (say, based on business analysts’ suggestions):

  • retrieve Star Wars most vulgar character
  • then retrieve homeworld of Star Wars most vulgar character
  • then retrieve Star Wars most awesome and impressive character
  • show error notifications (if any)
  • in any case show a joke about Chuck Norris at the end

OKAY, excellent scenario. But it requires some refactoring. Below you can find one of the possible implementations:

Star Callbacks Codepen Example

var mostVulgarStarWarsCharacter = 'https://swapi.co/api/people/3/';
var mostAwesomeStarWarsCharacter = 'https://swapi.co/api/people/4/';
var theGreatAndTerrible = 'https://api.chucknorris.io/jokes/random';

xhrRequest(mostVulgarStarWarsCharacter, function (character) {
    // R2D2 business logic
    console.log('BEEP, BOOP, BEEP! My name is ' + character.name + '. I am very rude droid. They beeped out every singe word I said.');
    xhrRequest(character.homeworld, function (homeworld) {
        console.log('Please note, I\'m from ' + homeworld.name + '. BEEP!');
        awesomeCharacter();
    }, function (error) {
        console.log('Oh bother! The Galaxy is in Danger again! ' + error);
        awesomeCharacter();
    });
}, function (error) {
    console.log('Oh bother! The Galaxy is in Danger again! ' + error);
    awesomeCharacter();
});

function awesomeCharacter() {
    // Darth Vader business logic
    xhrRequest(mostAwesomeStarWarsCharacter, function (character) {
        console.log('Meet ' + character.name + '! I\'m awesome and impressive. Most impressive!');
        chuckNorrisJoke();
    }, function (error) {
        console.log('Oh bother! The Galaxy is in Danger again! ' + error);
        chuckNorrisJoke();
    });
}

function chuckNorrisJoke() {
    xhrRequest(theGreatAndTerrible, function (chuckJoke) {
        console.log('BTW, did you know that ' + chuckJoke.value);
    }, function () {
        console.log('Don\'t mess with Chuck!');
    });
}

function xhrRequest(url, SuccessCallback, errorCallback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.send();
    xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) {
            return;
        }
        if (xhr.status === 200) {
            var response = JSON.parse(xhr.responseText);
            SuccessCallback(response);
        } else {
            var error = xhr.statusText || 'The reason is mysterious. Call Yoda!';
            errorCallback(error);
        }
    }
}

Not so neat anymore, yeah? We are sure the example can be improved, but it is likely to be a crap anyway. Business logic is spread, fragmented and mixed up with boilerplate infrastructural code. All intentions are buried under messy language constructions. Now imagine real production app which can contain much more complicated asynchronous logic. Sooner or later maintenance of such code will become a real pain. Well, congratulations! You are on the way to Callback Hell!

The solution

That’s time for promises to come on the stage (drum roll begins). Let’s omit technical details and move on to rewritten example. Don’t worry if you are not familiar with promises or ES6 features, you will find some explanations below. Just notice how laconic, simple, readable and meaningful the code is:

Star Promises Codepen Example

const mostVulgarStarWarsCharacter = 'https://swapi.co/api/people/3/';
const mostAwesomeStarWarsCharacter = 'https://swapi.co/api/people/4/';
const theGreatAndTerrible = 'https://api.chucknorris.io/jokes/random';

promisedRequest(mostVulgarStarWarsCharacter)
    .then(character => {
        console.log(`BEEP, BOOP, BEEP! My name is ${character.name}. I'm very rude droid. They beeped out every singe word I said.`);
        return promisedRequest(character.homeworld)
    })
    .then(homeworld => console.log(`Please note, I'm from ${homeworld.name}. BEEP!`))
    .catch(error => console.log(`Oh bother! The Galaxy is in Danger again! ${error}`))
    .then(() => promisedRequest(mostAwesomeStarWarsCharacter))
    .then(character => console.log(`Meet ${character.name}! I'm awesome and impressive. Most impressive!`),
          error => console.log(`Oh bother! The Galaxy is in Danger again! ${error}`))
    .then(() => promisedRequest(theGreatAndTerrible))
    .then(chuckJoke => console.log(`BTW, did you know that ${chuckJoke.value}`),
          error => console.log('Don\'t mess with Chuck!'));

function promisedRequest(url) {
    return new Promise(function (resolve, reject) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.send();
        xhr.onreadystatechange = () => {
            if (xhr.readyState !== 4) {
                return;
            }
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                const error = xhr.statusText || 'The reason is mysterious. Call Yoda!';
                reject(error);
            }
        };
    });
}

A bit of theory

  1. Promises are objects that represent state of some future, deferred tasks. Promise itself doesn’t make sense without a task. Usually you pass this task to promise constructor or call a factory which runs task, creates promise and returns it.
  2. Once you create promise (along with the task), your main flow continues execution. The task will be processed asynchronously.
  3. Internally promise can be in one of three states: pending (when associated task is not finished yet), fulfilled (after and if task finishes successfully), rejected (after and if task fails). Once promise changes its state, it can never change it again.

Asynchronous tasks and promises

  1. Promises have then() method which accepts two function parameters - onFulfilled() and onRejected(). onFulfilled() is called after and if the task finishes successfully, onRejected() is called if task fails. With this functions promise can notify main flow and pass task execution results onFulfilled(taskResultData), or task error onRejected(taskError).
  2. Most important thing in promises is chaining. Each then() method yields and returns new promise. This allows to create elegant sequences of dependent tasks.

Promise chains

Implementations

There was no native promises support in JavaScript until recently. That is why today we have a bunch of alternative implementations.

  • jQuery. jQuery supports deferred (future task) concept starting from 1.5 version.

  • AngularJS. AngularJS supports promises and has built-in $q service to handle asynchronous tasks. Furthermore, $http communication service is based on promises.

  • ReactJS. React doesn’t have baked in promise library, so you are free to choose 3-rd party implementation (or JS native, see below).

  • Other. There are lots of other implementations. E.g. Dojo is considered to be a pioneer in JavaScript promises, Q library by Kris Kowal is often used in Node.js along with Promised-IO by Kris Zyp, Bluebird is loved by many for its speed and productivity.

  • EcmaScript 6. Finally in ES 6 we have got standard build-in promise implementation. So if you are among the lucky ones who use ES 6 in your project - then consider using native promises even if you have alternative implementations in project-involved libraries. Some benchmarks show that ES 6 promises are memory-intensive and not the fastest. But we believe that it is going to change soon and from maintenance perspective usage of standard solutions is always better. Today support level of native promises is quite high:

Browser support of native JavaScript promises on September 2016

Use tips

1. Always return promises from methods with asynchronous tasks. Just make it a rule. If you use promise in a function/method - then this method must always return promise. Imagine a typical example: you need to send something to back-end and you don’t have any dependent tasks except notifying user using ‘toaster’ notifier. Seems like there is no sense to return promise from such method:

function sendToServer(data) {
    promisedPostRequest(data)
        .then(() => {
            showToaster('Successfully posted.');
        }, () => {
            showToaster('Posting failed.');
        });
}

But requirements change, that is why later the logic of the method may become much more complicated. And if one day you get a dependent tasks - then it can take a lot of efforts to refactor the code to fit promise pattern. Another good reason to follow this practice is testing. If you return promise from such methods - then you always have a hook to process it by automated tests. Below there is a better version of the example (there is a small difference in the code, but big difference in sense):

function sendToServer(data) {
    return promisedPostRequest(data)
        .then(() => {
            showToaster('Successfully posted.');
        }, () => {
            showToaster('Posting failed.');
        });
}

2. Flatten promise chains.

Many developers (especially those ones who just start using promises) build nested constructions like example below:

Nested promise statements

Don’t do this. Remember that each then() method returns new promise and you can resolve data to this new promise by returning this data from onFulfilled() method. Here is a more readable and elegant implementation of above example:

promisedGetRequest1()
    .then(data1 => promisedGetRequest2(data1.something))
    .then(data2 => data2.something)
    .then(data2Something => promisedGetRequest3(data2Something))
    .then(data3 => data3.something)
    .then(data3Something => promisedGetRequest4(data3Something))
    // ...
    .then(data100499 => data100499.something)
    .then(data100499Something => promisedGetRequest100500(data100499Something))

Or event better:

promisedGetRequest1()
    .then(data1 => promisedGetRequest2(data1.something))
    .then(data2 => promisedGetRequest3(data2.something))
    .then(data3 => promisedGetRequest4(data3.something))
    // ...
    .then(data100499 => promisedGetRequest100500(data1000499.something))

3. Use promise combinators. Most of JavaScript promise implementations have some helpers to deal with most common asynchronous tasks. For instance, you have several replicated sources with the same data and you want to send requests to all of them and proceed just after the very first response is received. Promise.race() combinator can be used in this case. You can pass several promises to this combinator and it will return a promise that resolves as soon as one of the passed promises resolves:

Promise.race([
    promiseA,
    promiseB,
    promiseC
])
    .then(promiseAorBorCresponse => {/* ... */});

Another useful combinator is Promise.all(). It returns a promise that resolves when all of the passed promises have resolved:

Promise.all([
    promiseA,
    promiseB,
    promiseC
])
    .then(promiseAandBandCresponses => {/* ... */});

4. Use catch() statement. Avoid using of onRejected() error handlers in then() method and replace them with catch(). Actually catch() is a shorthand of then(undefined, onRejected), but it makes code more clear and readable. So instead of this:

promised()
    .then(data => {
        // Do something with data
    }, error => {
        // Do something with error
    });

Better use this:

promised()
    .then(data => {
        // Do something with data
    })
    .catch(error => {
        // Do something with error
    });

What’s next

Promises are elegant and powerful functional programming tool. But you could have noticed how each new EcmaScript edition adds more object-oriented features to JavaScript. Predicting future evolution trends of JavaScript is difficult (who knows if OOPzilla will beat functionalKong, things change every day). But we were not surprised to see in upcoming EcmaScript 2017 specification a new asynchronous tool that suits OOP paradigm better. This tool is async/await functions. Despite the feature is still a proposal, it was already accepted into Stage 3 (‘Candidate’) and is going to be accepted into Stage 4 (‘Finished’) by the end of November 2016. If you are familiar with Python 3.5 or C# 5 (Task Parallel Library) - then probably you have already faced async/await feature. The construction helps to improve asynchronous statements drastically. It makes all asynchronous code (including exception handling!) clear, intuitive and self-descriptive by hiding all boilerplate stuff under the hood. We are not going to expand on the async/await definitions and details. You can find all the information on official Ecma github page. Just take a look on this two examples from official page and notice the difference in code amount and clarity. First one is written using regular promises, second one is written with async/await statements. Isn’t it great?

function chainAnimationsPromise(elem, animations) {
        let ret = null;
        let p = currentPromise;
        for(const anim of animations) {
            p = p.then(function(val) {
                ret = val;
                return anim(elem);
            })
        }
        return p.catch(function(e) {
            /* ignore and keep going */
        }).then(function() {
            return ret;
        });
    }
async function chainAnimationsAsync(elem, animations) {
        let ret = null;
        try {
            for(const anim of animations) {
                ret = await anim(elem);
            }
        } catch(e) { /* ignore and keep going */ }
        return ret;
    }

Summary

Of course, this article touches only the tip of the iceberg of promises and skips a lot of technical details, patterns and nuances. Promises are much deeper. It’s up to you and your team to use promises or not. In our opinion, it’s a brilliant technology and we highly recommend using it in your projects. Even if you don’t like promises, but they are enforced by chosen stack (framework or library), then don’t ignore their advantages. Always follow best promises’ practices and avoid anti patterns. Sooner or later you will get used and notice that with promises clarity and maintainability of your code is higher. Even if you will not fall in love with the technique, this knowledge and experience will come in handy since promises are widespread in JavaScript world.

Useful Links

  1. Promises/A+ Specification
  2. JavaScript Promises explanation by Forbes Lindesay
  3. Callback Hell
  4. jQuery Deferred
  5. AngularJS $q
  6. MDN Promise
  7. Promise Anti-patterns
  8. Async Functions for ECMAScript
SIMILAR POSTS
Nov 09 2016
Missed some news in Meteor world? Don't worry and check our digest!
Oct 19 2016
This article highlights basic methods which will help you in layout and creation of adaptive emails.
Oct 11 2016
Check out all September news!