Introduction to the Javascript Promises

Introduction to the Javascript Promises

A promise is a method that eventually produces a value. It can be considered as the asynchronous counterpart of a getter function. You can understand how it works with a simple example :

promise.then(function(value){
    // Do something with value
});

JavaScript is only asynchronous in the sense that it can make, for example, Ajax calls. The code will stop executing until the call returns (successfully or otherwise), at which point the callback will run synchronously. No other code will be running at this point. 

Thanks to the Promises API , developers are able to avoid a lot of callbacks (just taking a callback or passing a callback doesn't mean it's asynchronous) to handle async interactions, which now can be handled as any other variable.

Promises can replace the asynchronous use of callbacks, and they provide several benefits over them. They start to gain ground as more and more libraries and frameworks embrace them as their primary way to handle asynchronicity. Since 2013, the promises are natively supported in the modern Browsers.

Why should i use promises

The promises will help you to make all the complex async code easier to handle, understand and maintain.

Promises are not about callback aggregation. That's a simple utility. Promises are about something much deeper, namely providing a direct correspondence between synchronous functions and asynchronous functions.

The javascript Promises represent the next great paradigm in JavaScript programming, but understanding why they are so great is no simple matter so please keep reading.

Creating your first promise

To understand the concept, we'll expose the most simple way to use a Javascript Promise.

The Promise constructor takes one argument, a callback (function) with two parameters, resolve and reject. Now inside the callback you need to execute all that you want do asynchronously, finally if everything worked correctly invoke the resolve function, otherwise the reject (error) function.

Create your first promise based in the following snippet :

var myFirstPromise = new Promise(function(resolve,reject){

    // Do an asynchronous task here like a fetch or an ajax call
    // Whatever you want
    // then resolve it or reject it

    var accepted = true;
    
    // add some condition to know which to send (success or error)
    if(accepted){
        resolve("Send this value");
    }else{
        reject("Send this error");
    }
});

myFirstPromise.then(function(successData){
    // Outputs : "Send this value"
    console.info(successData);
}).catch(function(errData){
    // Outputs : "Send this error"
    console.error(errData);
});

All promises instances will have a then method which allows you to do something when the promise is fulfilled. The then method callback receives the result given to it by the resolve execution.

You can attach more than one then callback to the promise. Every then appended callback will be consecutively executed to the order in which they were assigned.

The only difference, is that after the first then, the received value for every appended then, will be the value returned by the previous then i.e :

var anyPromise = new Promise(function(resolve,reject){
    resolve("Send this value");
});

anyPromise.then(function(successData){
    // Outputs : "Send this value"
    console.info(successData);
    return "But now, the following then receives this string";
}).then(function(data){
    // Outputs "But now, the ...."
    console.log(data);
    return 1000;
}).then(function(data){
    // Outputs 1000
    console.log(data);
});

The catch callback is executed when the promise is rejected by the reject execution. You can even instead of use the catch callback, provide a second parameter to the then function.

myFirstPromise.then(function(data) {
    console.log(data);
}, function(err) {
    console.error(err);
});

Note: only 1 error callback will be triggered, either the function as second parameter of the then function or the catch callback, not twice.

Promise.resolve & Promise.reject

Sometimes, for different reasons you'll don't need to repeat an async task, i.e an ajax call which retrieves something and it can be stored in memory. That's when resolve and reject comes to play.

Use these methods to return the result of a promise without use the entire constructor (fulfilled or rejected).

var cacheUserAge = {};

function getUserAge(id){

    // if user id is in the object
    // do not fetch the result from the server
    // return the local result instead
    if(cacheUserAge[id]){
        return Promise.resolve(cacheUserAge[id]);
    }

    // use Promise.reject to trigger the catch callback

    return fetch('api/userage/' + id + '.json')
    .then(function(data) {
        // save it in the object
        cacheUserAge[id] = data;
        // return the value to the next then callback
        return result;
    }).catch(function() {
        throw new Error('Could not get age: ' + id);
    });
}


// Then use it
var userId = 15;

getUserAge(userId).then(function(age){
    alert(age);
});

Note: the fetch api returns a promise.

Fulfilling multiple promises at time

The promise API allow you to solve multiple promises at time using the Promise.all method.

Promise.all takes an array of promises and creates a promise that fulfills when all of them successfully complete. You get an array of results (whatever the promises fulfilled to) in the same order as the promises you passed in.

var firstPromise = new Promise(function(resolve,reject){
    resolve("First value");
});

var otherPromise = new Promise(function(resolve,reject){
    resolve("Other value");
});

var toFulfill = [firstPromise,otherPromise];

// Outputs ["First value","Other value"]
Promise.all(toFulfill).then(function(arrOfResults){
    console.info(arrOfResults);
});

Note: add the catch callback to the Promise.all statement to catch errors. If any of the given promises in the array fails, the then callback will be not triggered but catched.

Promise.race

Yes, a literal race between promises. This function is the opposite to Promise.all because it only return the result (or the reject) of the first promise that is fulfilled, the others will be ignored.

Note: if you know how can be this feature useful in production, please share it in the comment box.

var p1 = new Promise(function(resolve, reject) { 
	setTimeout(function() { resolve('p1!'); }, 5000);
});

var p2 = new Promise(function(resolve, reject) {
	setTimeout(function() { resolve('p2!'); }, 10000);
});

// Let's run
Promise.race([p1, p2]).then(function(winner) {
    // Outputs p1!
	console.log('The winner is: ', winner);
}).catch(function(one, two) {
	console.log('Catch: ', one);
});

Cross browser support

The Promise API is available since Chrome 32, Opera 19, Firefox 29, Safari 8 & Microsoft Edge, promises are enabled by default on them.

Polyfill

As mentioned before, the introduction of the API doesn't cover out-dated browsers, but there are many polyfill implementations available for use. We recommend you to use the es6-promise polyfill which can be retrieven in the official repository of stefanpenner on Github here.

The polyfill covers all the possible scenarios of the API, however there is a limitation in IE9 which can be easily solved using a different syntax ().

catch is a reserved word in IE<9, meaning promise.catch(func) throws a syntax error. To work around this, you can use a string to access the property as shown in the following example :

promise['catch'](function(err) {
  // ...
});

// Or just use the second paramer of then instead of using catch
promise.then(undefined, function(err) {
  // ...
});

Note: such technique is already provided by most common minifiers, making the resulting code safe for old browsers and production. However, to prevent any possible incompatibility, use the second parameter of the then function instead of use catch.

There are plenty of implementations of promises available to developers. For example, jQuery's Deferred, Microsoft's WinJS.Promise, when.js, q, and dojo.Deferred. However be aware of which you use as not all of them follow the standards, jQuery's implementation does not quite fulfill the Promises/A spec in the way that most of the other implementations do.

Start using promises now ! Have fun

Become a more social person