I like to keep my Promises (when I Javascript)

Reading Time: 2 minutes

Promises are now here with ES6 in Node/iojs, Chrome, FireFox, and Safari. Easy polyfills exist for the browsers that don’t yet support A+ promises. So how would you use them?

What do I promise you?

Promises arose from the need to handle nested callbacks and compose actions. Ever try to $.ajax in a success handler of a $.ajax — you end up half of the screen width taken up with just indentation! Or say you need the results of multiple async operations to finish computing something. You can collect all the operations into one promise and act upon the result instead of having to keep track of that yourself.

Promise me some files!

Let’s work on an example of building a Promise API from an existing async API. The fs.readFile API in Node.js is an excellent example of a callback based API.


var fs = require('fs');

try {

    fs.readFile('package.json', function(err, data) {

        if (err) throw err;

        console.log(data.toString());

    }); 

} catch (ex) {

    console.log("Error! " + ex);

}

I’m sure that code has a familiar ring to it — but what if we wanted to compose many file reads together? The easiest thing to do is turn this into a Promise based API, like…


var fs = require('fs');

function readFilePromise(filename) {

    return new Promise(function (fulfill, reject) {

        fs.readFile(filename, function (err, data) {

            if (err) reject(err);

            else fulfill(data.toString());

        });

    });

}

And we can exercise this API…


readFilePromise('package.json').then(function(data) {

    console.log(data);

}, function(err) {

    console.log("Error! " + err);

});

You can chain promises together. Perhaps we need to query a config to find a file’s location before reading it. And the best part, you only need to write one error handler – an error in any part of the chain will end up executing the error handler.


readFilePromise('finder.txt').then(function(data) {

    return readFilePromise(data);

}).then(function(data) {

    console.log(data);

}, function(err) {

    console.log("Error! " + err);

});

Promise.catch allows you to compose the code that leaves error handlers explicit.


readFilePromise('finder.txt').then(function(data) {

    return readFilePromise(data);

}).then(function(data) {

    console.log(data);

}).catch(function(err) {

    console.log("Error! " + err);

});

What if we just needed two files loaded? We can collect the promises together into a single promise.


Promise.all([

    readFilePromise('package.json'),

    readFilePromise('README.md')

]).then(function(data) {

    // in this case, data is an array of items

    for(var d of data) {

        console.log(d);

    }

});

Explanations are boring, show me something real

Sure, I’ll show you something real. Consider the case where you’re working on a Connect add-on for JIRA. Then you are using AP.request to load something about a project from JIRA’s REST API. But you don’t know the project key pragmatically – you just know it’s the first project.

First we’d need to promisify (if it isn’t a word, it will be shortly) AP.request


function promisified_request(url, type, data) {

    return new Promise(function(fulfill, reject) {

        AP.require('request', function(request) {

            request({

                url: url,

                type: type || "GET", // default to get

                data: data,

                success: function(data) {

                    fulfill(data);

                },

                error: function(err) {

                    reject(err);

                }

            });

        });

    });

}

And we can use a function like that to compose a chain of events together…


promisified_request('/rest/api/latest/project').then(function(data) {

    // get more details about the first project

    return promisified_request('/rest/api/latest/project/' + data[0].id);

}).then(function(data) {

    AP.require('message', function(data) {

        message.info('This project has ' + data.components.length + ' components.');

    });

}).catch(function(err) {

    alert('Error! ' + err);

});

Promises are now in Nodejs, and will be in all browsers real soon. There’s no reason for you not to use them if you see the value they provide. Go forth and Promise!

Thanks for reading and let us know what you think at @atlassiandev or at @travisthetechie.