DEV Community

Cover image for All (or just most) of what you need to know about handling Promises
Ori Volfovitch
Ori Volfovitch

Posted on • Updated on

All (or just most) of what you need to know about handling Promises

I Don't use Promises on a daily basis. But when I do, all I need is a simple usage example of how to handle them. What I find instead are over complex examples, many articles about async programming and the theory behind Promises in JavaScript.
I end up re-learning Promises for a few minutes to realize that in most cases there are only 2 - 3 main ways I actually handle the Promises.

So, I made a Github repository called "Promise Sandbox" with the 3 main usages that I need, and made sure that the code will be as short and as simple as possible.
In this example, I only execute 2 Promises. A long one and a short one.
You can add more but it will only make the code longer and more complex, and it will not get you any more insights.

Let's dive straight into code!

Promise execution

So, I am executing 2 simple Promises here. currently set to resolve (you can flip the annotations if you would like them to be rejected).
longPromise - set to be resolved after 10 seconds.
shortPromise - set to be resolved after 2 seconds.

var longPromise = ()=>{
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('longPromise resolved');
            // reject('longPromise rejected');
        }, 10000);
    })
};

var shortPromise = ()=> {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve('shortPromise resolved');
            // reject('shortPromise rejected');
        }, 2000);
    })
};

Handling Options

Classic chaining

The good old way to handle Promises.
This is a shortcut to find yourself in callback hell.

longPromise()
    .then((data)=>{
        console.log(data); // logs: longPromise resolved
        shortPromise()
            .then((data)=>{
                console.log(data) // logs: shortPromise resolved
            })
            .catch((data)=>{
                console.log(data) // logs: shortPromise rejected
            })
    .catch((data)=>{
        console.log(data) // logs: longPromise rejected

        // shortPromise is unhandled in case longPromise was rejected
    });
});

Edited:

As bloggers Eugene Karataev and Joel Ruiz suggested, we can dodge callback hell simply by returning the following Promise, instead of just calling it.
This will flatten our nesting and make the code much more readable:

longPromise()
    .then((data)=> {
        console.log(data); // logs: longPromise resolved
        return shortPromise();
    })
    .then((data)=>{
        console.log(data) // logs: shortPromise resolved
    })
    .catch((error)=> {
        console.log(error); // One catch to reject them all!
    });

Promise all

Batching all promises into an array. Handling all at once.
If you are using Node, i suggest you JSON.stringify the logged data.

Promise.all([longPromise(), shortPromise()]).then(function(data) {
    console.log(data);
});

Async await (Thank you ES6!)

Or actually ES8, to be more accurate.
If you don't want to deal with the async quirks of JS, let the new async-await functionality to take care of it. make sure you wrap your awaits in an async function.
In this example, i made the async function to be an IIFE to make sure that it invokes Immediately.

(async ()=>{
    let long = await longPromise();
    let short = await shortPromise();
    console.log(long);   // logs: longPromise resolved
    console.log(short);  // logs: shortPromise resolved
})();

This should work fine on any browser (Except IE. IE is not a browser!).

All the rest that was not mentioned

  • If you don't care whether the promise is resolved or reject, when handling a single promise use .finally() instead of .then() and/or .catch().
  • In addition to Promise.all() you also have Promise.race() which is like Promise.all(), but will be invoked once the first promise is fulfilled.
  • There is also the Promise.allSettled() which is still new, and not yet supported by most browsers and Node below version 12.9.

Discussion (14)

Collapse
karataev profile image
Eugene Karataev

What's the reason to have promise hell in the first example when you can just chain promises one after another?

longPromise()
    .then((data)=>{
        console.log(data); // logs: longPromise resolved
        return shortPromise();
    .then((data)=>{
        console.log(data) // logs: shortPromise resolved
    })
});
Collapse
orivolfo profile image
Ori Volfovitch Author • Edited on

Not sure what you mean.
You just removed the .catch(), which means you do not handle in case of rejection.

Collapse
joetex profile image
Joel Ruiz • Edited on

You can chain the Promises. Inside of .then(), you can return another promise and it'll let you add another .then() underneath. You add a single .catch() at very end, which will work for all the .then() chains.

longPromise()
    .then((data)=>{
        console.log(data); // logs: longPromise resolved
        return shortPromise();
    .then((data)=>{
        console.log(data) // logs: shortPromise resolved
    })
    .catch((error)=> {
         console.log(error);
    })
});
Collapse
karataev profile image
Eugene Karataev

Yeah, as Joel mentioned, you can return promises to get one flat promise chain.
Imagine that you have 4 promises. If you follow example 1 in your post, you'll get promise structure like callback hell, but if you return next promise in every then, you'll get nice and flat promise chain.

Thread Thread
orivolfo profile image
Ori Volfovitch Author • Edited on

Oh, now I understand.
Wow, this is worth editing the original post!

Thanks!

Thread Thread
efishtain profile image
efi shtain

Another improvement you can make is not using anonymous function
It gives you testability and readability
So instead of:

longPromise()
.then((data)=>{
console.log(data); // logs: longPromise resolved
return shortPromise();
.then((data)=>{
console.log(data) // logs: shortPromise resolved
})
.catch((error)=> {
console.log(error);
})
});

you can do:
async function firstHandler(data){
console.log(data)
return shortPromise()
}

longPromise.then(firstHandler).then(console.log).catch(console.log)

Thread Thread
orivolfo profile image
Ori Volfovitch Author

But wouldn't this example start to get ugly if you had more that just 2 Promises?

Thread Thread
efishtain profile image
efi shtain

getData()
.then(processData)
.then(convertData)
.then(saveData)
.then(notifyOnSuccess)
.catch(handleError)

looks ok to me
any how I'd go with async await today as much as I can, only use native promises if I need to wrap a callback from a library or something like that

Thread Thread
orivolfo profile image
Ori Volfovitch Author

Nice combo.
So you are actually chaining them asynchronously?

Thread Thread
efishtain profile image
efi shtain

what do you mean?
you can't processData before you got the data, so it has to be done in series
getData() has to return a promise, the rest of the functions would automatically be converted to async

Thread Thread
orivolfo profile image
Ori Volfovitch Author • Edited on

So why in your firstHandler example did you made the function to be async?

Thread Thread
efishtain profile image
efi shtain

Optional.
If you use it elsewhere, and it is async, mark it as async.
only when I use anonymous function directly in the chain I won't mark methods with async unless I have to.

Collapse
iamthehttp profile image
IamTheHttp

Hey Ori,
Looks good,
I'd add a few 'Gotchas' and maybe some more information here!

  1. When using async/await it's very difficult to use finally and catch, async await only deals with 'resolve'

  2. Promises are a great way (and I think the only way) to add a micro task into the event loop, micro tasks are the only way to ensure execution before the next tick. (done with Promise.resolve())

  3. You could probably await a Promise.all() as well, as it returns a promise

Other than that, good stuff!

Collapse
orivolfo profile image
Ori Volfovitch Author

Thanks!