DEV Community

Cover image for How I use JavaScript Promises
Shubhojyoti
Shubhojyoti

Posted on • Originally published at shubho.dev

How I use JavaScript Promises

Originally published at Shubho.Dev

Async programming in JavaScript was scary for me. The only async paradigm I was comfortable with was jQuery's $.ajax. However, I went full vanilla JavaScript for the past 8 years, and when I started working with NodeJS, I had to learn Promises. I haven't dabbled much with third-party libraries like Bluebird. I have the most experience with native Promise.

My main issue with Promise (or asynchronous paradigm in general) used to be when I wanted to execute statements after the Promise statement began. It took some time to realise that once a Promise statement fires, there is no way to cancel it. Another issue was Promise chaining. This one was a kicker. My earlier functions with Promises always looked like friends of callback hell. After all these years and working on a couple of big projects, I can safely say I love Promises. Even though async/await is the new fad, I still love Promises.

So here is how I use Promises to make my coding life simpler.

Create a Promise skeleton

Whenever I create a new function that returns a Promise, I create the skeleton first. Since the function cannot return anything other than Promise based values, I always wrap all the statements of the function within the Promise.

function sample() {
    return new Promise(function(resolve, reject) {
        // The function body
    });
}
Enter fullscreen mode Exit fullscreen mode

The above sample function wraps its entire statement within the Promise, returning immediately. You can either resolve() or reject() the output you want from the body. This way, I never make a mistake of not returning a Promise. It also helps me in creating Promise chains. Whenever in a chain, I realise I need a new function, I create the skeleton name it appropriately and finish the main chain. Then I come back one by one and finish the individual functions.

Promise chains - Points to remember

Promise chaining is tricky. If we are not careful, we can have a new type of callback hell. An example:

function promiseCallback() {
    return new Promise((resolve, reject) => {
        aNewFunction()
            .then((values) => {
                someOtherFunction(values)
                    .then((someOtherValue) => {
                        // Do something
                        resolve(someOtherValue);
                    })
                    .catch((err1) => {
                        // Error in inner function
                        reject(err1);
                    });
            })
            .catch((err) => {
                // Error in outer function
                reject(err);
            });
    });
}
Enter fullscreen mode Exit fullscreen mode

In the above sample aFunction() and someOtherFunction() are two functions returning Promises. If you see carefully, the sequence looks like a callback hell. The inner then and catch the chain, and outer ones are independent. We cannot handle errors in a common catch block, and we need to be careful that the inner functions are always the last line within their outer then() otherwise we can't control the execution flow.

A better way with chains:

function promiseCallback() {
    return new Promise((resolve, reject) => {
        aNewFunction()
            .then((values) => {
                return someOtherFunction(values);
            })
            .then((someOtherValue) => {
                // Do something
                resolve(someOtherValue);
            })
            .catch((err) => {
                // Error in outer function
                reject(err);
            });
    });
}
Enter fullscreen mode Exit fullscreen mode

Returns within the then chain can only have three types:

  1. Promise - A then function in a chain can return a Promise. It's result is passed to the next then.
  2. Scalar Value - A then function in a chain can return a value like a String or a Number. This value is passed to the next then as is and the chain can continue.
  3. Throw - A then function can throw an error, which moves the execution to the catch block.

As long as all your returns within a then follow the above three types, you shouldn't have issues following your Promise chain.

Note
Remember to always resolve() or reject() in the last then or catch of the chain.

When to create a new Promise function

Within a Promise chain, if there are multiple if-else conditions, and each condition can lead to different Promise results, it is an excellent time to create a new function that returns a Promise. This way, the Promise chain returns a single statement calling the new function.

Handling a scalar value or a Promise function in one step

Assume we have a function which gets the marks attained by a student using his roll number. However, the function either takes a roll number as an input or the name of the student. The marks can be attained from the DB only using the roll number. Here is some pseudo-code.

function getMarks(obj) {
    let rollNumberPromise = null;
    if ('rollNumber' in obj) {
        rollNumberPromise = Promise.resolve(obj.rollNumber);
    } else if ('studentName' in obj) {
        rollNumberPromise = getRollNumberFromName(obj.studentName);
    }

    if (!rollNumberPromise) {
        reject('Nothing worked');
    }

    rollNumberPromise
        .then((rollNumber) => {
            return get_marks_from_db(rollNumber);
        })
        .then((marks) => {
            resolve(marks);
        })
        .catch((err) => {
            reject(err);
        });
}

function getRollNumberFromName(studentName) {
    return new Promise(function(resolve, reject) {
        fn_to_get_roll_number_from_db(studentName)
            .then((rollNumber) => {
                resolve(rollNumber);
            })
            .catch((err) => {
                reject(err);
            });
    });
}

function fn_to_get_roll_number_from_db(studentName) {
    return new Promise(function(resolve, reject) {
        // some code
    });
}

function get_marks_from_db(rollNumber) {
    return new Promise(function(resolve, reject) {
        // some code
    });
}
Enter fullscreen mode Exit fullscreen mode

getMarks(obj) takes an Object as an input. We create a local variable rollNumberPromise. If the rollNumber is already present, we save the value in the variable using Promise.resolve(). This creates a Promise which resolves when called with the value. If student’s name is sent, then we save the call to the function getRollNumberFromName(studentName) to the local variable. Calling rollNumberPromise.then() returns a rollNumber whether it is received from the DB or sent directly as input to the function. Using it this way ensures that getMarks() has a single Promise chain, rather than an if-else condition based on whether the input passed was a number or a name.

Invoke a Promise at the end

As mentioned before, once a Promise, once invoked, cannot be cancelled. Any statements which do not depend on the Promise output and which can be carried out independently without an async call should complete before you start a Promise chain in your function. Once a Promise chain begins, any subsequent steps must be within the then chain. The only exception to this is when you do not care of the Promise value, and you want the Promise to execute in the background while your primary function keeps running.

Conclusion

Promises are difficult. However, with practice and following some rules, it makes working with them a charm. I strictly follow the above rules, and I never go wrong with Promises these days. Find out what you are comfortable with and create your own rules.

Top comments (8)

Collapse
 
andrevinsky profile image
Andrew Revinsky

Hi,

on your template, you seem to be enjoying the verbosity. If not, I recommend:

function sample() {
    return new Promise((rs, rj) => {
        // call rs() or rj() and bail out
        // if no need for rj(), use only rs
    });
}

and since you are using arrow functions, do you always use parentheses? This:

    (rollNumber) => {
                resolve(rollNumber);
     }

becomes..

rollNumber =>resolve(rollNumber)

Wait, what?!

This:

.then((rollNumber) => {
                resolve(rollNumber);
            })

.. easily becomes this:

.then(resolve)

Back to your promise template. Here's a much lighter version. You don't need to reject, you throw:

function sample() {
    return new Promise(rs => {
        // your stuff
    });
}

Then on this monstrosity:

rollNumberPromise
        .then((rollNumber) => {
            return get_marks_from_db(rollNumber);
        })
        .then((marks) => {
            resolve(marks);
        })
        .catch((err) => {
            reject(err);
        });

Consider changing it into:

rollNumberPromise
        .then(get_marks_from_db)
        .then(resolve)
        .catch(reject);

It also helps to familiarize with async/await constructs.

You concluded that 'Promises are difficult'. Please try to understand the main reason they exist (I am not telling you that now, sorry) - you will then see they are not so difficult at all.

Collapse
 
shubho profile image
Shubhojyoti

Thanks for reading. The verbosity was intentional. The point was to show how to use Promises while writing functions as a beginner. Using arrow functions surely uses less lines of code and I use that in my actual projects. However explaining Promises to my juniors using arrow function was always difficult. I am familiar with async/await constructs. I still prefer Promises and use it as much as possible. But my colleagues who came from Java background they preferred async/await more 😀

Collapse
 
cuitpoisson profile image
cuitpoisson

Maybe the point of the article was to show how simple it is to wrap everything in a promise, but starting with the original:

function getRollNumberFromName(studentName) {
    return new Promise(function(resolve, reject) {
        fn_to_get_roll_number_from_db(studentName)
            .then((rollNumber) => {
                resolve(rollNumber);
            })
            .catch((err) => {
                reject(err);
            });
    });
}

And combining with your suggestion (and doing the same with reject):

function getRollNumberFromName(studentName) {
    return new Promise(function(resolve, reject) {
        fn_to_get_roll_number_from_db(studentName)
            .then(resolve)
            .catch(reject);
    });
}

We can see that the then is just returning the value from the original promise and the catch is also just rejecting with the already rejected value. That means we're just returning the original promise, which can be simplified to:

function getRollNumberFromName(studentName) {
    return fn_to_get_roll_number_from_db(studentName);
}

Which is just the fn_to_get_roll_number_from_db function.

Collapse
 
jaakidup profile image
Jaaki

I'll tell you one thing, that Promise chain example you gave is one of the main reasons prefer other languages over JS. That can easy make some scary ugly monster code !

Collapse
 
mongopark profile image
Ola' John Ajiboye

This isn't really a problem with JS. Like others have shown it can be significantly simplified.

Collapse
 
jaakidup profile image
Jaaki

Sure, I must say that at least the new Async Await is cleaner, which is somthing I've actually used much more than Promises, as I spend more time in Go

Collapse
 
zeddotes profile image
zeddotes

Seems like callback hell awaiting. Why not use async/await?

Collapse
 
nadim profile image
Nadim

Thank you for your awesome post, I prefer async/await it's much more easier for me.