loading...

Batman and Promises

jckuhl profile image Jonathan Kuhl ・4 min read

Alfred

Imagine you're the CEO of some business. Wayne Enterprises I suppose. Here you are putzing around in the Batcave trying to figure out what the Joker's going to do next, but you, Mr. Wayne, have a hankering for some caffeine. So you fetch Alfred and tell him, "get me a coffee."

So Alfred goes to get you a coffee. While he's out and about, you're still free to continue polishing the Batmobile and searching for information on the Joker on the Batcomputer and fixing the holes and tears in your Bat armor.

After a little while, Alfred returns.

Promises are Alfred. A Promise is a task JavaScript performs with an assurance that it will, at some point, return with some value. It might not be the value you want, but it will be some sort of value. Promises are asynchronous. Actions passed to a Promise are put on the event loop rather than the function stack, which allows the other functions in your code to continue executing while you await your promise.

Here's a basic Promise:

new Promise((resolve, reject) => {
    doTheThing((error, value)=> {
        if(error) {
            reject(new Error('something broke'));
        } else {
            resolve(value);
        }
    });
});

We have some function, doTheThing() that gets some value. What it is doesn't really matter. Maybe it's an API call or something. Either way, the basics work like this. If you get the value you want, you pass it to the resolve() function. If you don't get the value you want, you call reject() and pass it an error or some other value you'd want if the Promise returns an error.

Any object passed to resolve, is then passed to the callback function of any then() call. then() is fired immediately after resolve() and will perform the action you desire to have happen once your Promise is resolved. catch() works with reject(). catch() also takes a callback and the value passed into reject() is then passed into the callback catch() uses. If you have multiple promises and multiple then() methods chained onto it, catch() is a one stop shop for all errors that are thrown; you only ever need one catch().

So to rewrite this in terms of our Alfred example:

const coffee = new Promise((resolve, reject) => {
    getCoffee((error, coffee)=> {
        if(error) {
            reject(new Error('no coffee!');
        } else {
            resolve(coffee);
        }
    });
});
coffee.then((coffee) => drink(coffee))
    .catch((error) => console.error(error.msg));

Alfred comes back and says he has your favorite coffee! Your Promise is resolved and you can drink the coffee. Alfred comes back and tells you he failed to get coffee because the Joker blew up all the coffee shops. Your Promise has been rejected. You get mad, suit up and head out in the batmobile to handle this . . . error.

Sandwiches

After handling the Joker, you are hungry. You want a sandwich! But not just any sandwich. The Batman has a sophisticated taste after all. So you tell Alfred, that you want cheese from a specialty cheese shop, meat from your favorite deli, and fresh bread from your favorite baker.

Alfred now has to handle three promises at once.

This is where Promise.all() comes in. Promise.all() handles an array of promises and will not resolve until all the promises in the array are resolved. If any one of them are rejected, they are all rejected.

const sammich = Promise.all([getBread, getCheese, getMeat]);
sammich
    .then((nom) => eat(nom))
    .catch((error) => console.error(error.msg));

When Alfred returns, you either have all three parts to your sandwich, or you don't have a sandwich.

But why get the individual ingredients one at a time when there are so many delicious Gotham City sandwich shops to choose from? You decide you want a sandwich, but you can't decide between three different stores, so you'll just eat the first sandwich that is returned from you. You send Alfred, Robin, and Gordon out separately to get you a sandwich.

This is Promise.race(). Promise.race() takes an array of Promises and returns the first one to be resolved, skipping any that get rejected. Only if all of them are rejected, will Promise.race() be rejected.

const sammich = Promise.race([sandwich1, sandwich2, sandwich3]);
sammich
    .then((nom) => eat(nom))
    .catch((error) => console.error(error.msg));

Robin came back first, so you eat his sandwich.

Async/Await

But here's the thing, using .then and .catch get clunky. Can we get around this? You are after all, The Batman and your batcave could use a little cleaning up.

Async/Await offers some clean up. It allows for your flow control in your code to feel more natural. The way it works is simple, but it only works on Promise objects. If you mark a function as async and mark a line inside that function with await, execution of that function will halt on that line while waiting for that promise to be resolved or rejected. Meanwhile, all the other functions will keep executing. The execution of the async function is thrown into the event loop and will only be executed once a value is returned and the function stack is cleared.

While Alfred, Gordon and Robin are going about getting your sandwiches, you're free to tinker about the batcave and do whatever you want while you wait, so long as what you want to do doesn't require those sandwiches.

await is exactly what it sounds like, the execution must await the resolution or rejection of this promise. Any rejection is picked up by the catch block of a try/catch.

async function waitForCoffee() {
    let coffee;
    try {
        coffee = await getCoffee();
        coffee.drink();
    } catch(error) {
        console.error(error.msg);
    } finally {
        thank('Alfred').forServices();
    }
}

We're assuming getCoffee() returns a Promise in this example.

Here you have a more top to bottom flow of control with async/await. There's no other benefit to using async/await over .then()/.catch(), it isn't faster or necessarily more performant. It's just easier to read and manage.

Just be aware that async/await only works on Promises. If you try using it on something like the geolocation API, it won't work, unless you wrap those functions in Promises first.

That is Promises in a nutshell.

Posted on by:

jckuhl profile

Jonathan Kuhl

@jckuhl

I am a software development engineer in test for Infosys. My job is officially to write automated tests in Selenium Webdriver. I'm also a web developer as a hobbyest

Discussion

pic
Editor guide
 

Love the metaphor. I think those who didn't quite get the promises might understand better now, and hopefully confidently say, once they await their own example : I'm Batman :) . For those who do know how to work with promises it's still satisfying to read the article. :)

 

A really great article! I didn't know Promise.race() until now. I'm not sure how to apply it in real-life situations as of now, though. But atleast! =) Please keep making articles like this. =)

 

Cant believe this only has 14 hearts. This needs more love.

I didn't know about race, very neat!