An Introduction to Promises in JS

Javascript Promises are not difficult. However, lots of people find it a little bit hard to understand at the beginning.

There are a lot of definition of promise

  • The promise is commonly defined as a proxy for a value that will eventually become available. Promises are one way to deal with asynchronous code, without writing too many callbacks in your code.
  • The Promise is a way of defining a function in such a way that we can synchronously control its flow (resolve and reject).
  • The Promise object is used for deferred and asynchronous computations. A Promise represents an operation that hasn’t completed yet, but is expected in the future.

A promise has 3 states:

  • pending
  • fulfilled
  • rejected

Simply put, a promise is an object that holds a value that is promised to be given to you at some point in time. One can retrieve the value a promise is holding by calling the promises then method. A promise starts out in the pending state, then on success it will be in the fulfilled state, or if an error occurs, it will be in the rejected state.

Promise syntax look like this

new Promise(function (resolve, reject) { ... } );

or in visualization form

js_promises.png

Let's look at Promise in real world example.

Imagine you are a kid. Your mom promises you that she'll get you a new phone next week. You don't know if you will get that phone until next week. Your mom can either really buy you a brand new phone, or stand you up and withhold the phone if she is not happy.

That is a promise. A promise has 3 states. They are:

  1. Promise is pending: You don't know if you will get that phone until next week.
  2. Promise is resolved: Your mom really buy you a brand new phone.
  3. Promise is rejected: You don't get a new phone because your mom is not happy.

Let's convert this to JavaScript. Example in ES5

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

What you need to remember is, when the result is successful, call resolve(your_success_value), if the result fails, call reject(your_fail_value) in your promise. In our example, if mom is happy, we will get a phone. Therefore, we call resolve function with phone variable. If mom is not happy, we will call reject function with a reason reject(reason).

Now that we have the promise, let's consume it.

willIGetNewPhone
    .then(function (fulfilled) {
        // you got a new phone
        console.log(fulfilled);
    })
    .catch(function (error) {
        // mom don't buy it
        console.log(error.message);
    });

Example in ES6

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { 
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

willIGetNewPhone
    .then(fulfilled => console.log(fulfilled)) 
    .catch(error => console.log(error.message));

Notes that all the var are replaced with const. All the function(resolve, reject) has been simplified to (resolve, reject) =>.

This is an another example of invoking a Promise-based function waitForIt()

function waitForIt(n) {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve('DONE'), n);
    });
}

waitForIt(5)
    .then(x => console.log('Result: ' + x))
    .catch(error =>
        console.log('There has been a horrible mistake!', error);
    })

Promises are chainable

Let's say, you, the kid, promise your friend that you will show them the new phone when your mom buy you one.

That is another promise. Let's write it!

var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
            resolve(message);
        }
    );
};

Notes:

  • In this example, you might realize we didn't call the reject. It's optional.
  • We can shorten this sample like using Promise.resolve instead.
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

Let's chain the promises. You, the kid can only start the showOff promise after the willIGetNewPhone promise.

willIGetNewPhone
.then(showOff) 
.then(function (fulfilled) {
        console.log(fulfilled);
     // output: 'Hey friend, I have a new black Samsung phone.'
    })
    .catch(function (error) {
        // oops, mom don't buy it
        console.log(error.message);
     // output: 'mom is not happy'
    });

Takeaways

  • Promises are a means for turning an asynchronous function into a synchronous function.
  • By using a Promise, we can more easily control when a function tells the running program to continue.
  • With their chaining syntax, Promises allow us to nest calls to other functions (promise-based or otherwise) in an easy to follow, step-by-step process.

Useful links