Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
async function example() {
// you can catch rejected promises that you await on
try {
await Promise.reject('blargh');
} catch (err) {
console.log(err);
}
// if you don't catch, then `example()` resolves to a rejected promise
await Promise.reject('blah')
}
example().catch(err => console.error(err));
@joescii

This comment has been minimized.

Copy link

joescii commented Sep 22, 2016

I wasn't aware of what you just said in line 9. What does this buy us over just having a promise in the first place? I know this is just a toy example, but I don't see it.

@jmar777

This comment has been minimized.

Copy link
Owner Author

jmar777 commented Sep 22, 2016

@joescii

What does this buy us over just having a promise in the first place?

As you pointed out, there's really no practical improvement over the status quo in this example (although I do think there are some abstract advantages... I'll touch on those later). That being said, async/await does offer some very substantive practical advantages when it comes to logic branches. Consider this example:

// note: i realize you wouldn't normally do all of this in a route handler,
// so... just consider this the contriver's advantage :)
router.get('/users/:id', async function(req, res, next) {
    const userId = req.params.id;

    // first try to fetch from cache
    let user = await cache.get(userId);

    // if we had a cache miss...
    if (!user) {
        // fetch from the database
        user = await db.users.get(userId);

        // if we still didn't get a user, then 404
        if (!user) return res.status(404).end();

        // otherwise, cache the user for next time
        await cache.set(userId, user);
    }

    // yay, now we have a cached user
    res.json(user);
});

// note: this example assumes that `router` is an implementation that
// knows how to deal with promise-returning middleware/handler functions.
// E.g., https://github.com/jmar777/node-async-router

While I would wager that implementing this using nothing but ES6 Promises would get gnarly pretty quickly, I'll also admit that I've never ceased to be impressed with the flexibility of feature-rich (albeit non-standard) Promise implementations such as bluebird.

Here's my issue with that, though (and this is where I get into the more abstract side of the argument for async/await): there's no law of nature that states that "if some code includes asynchronous operations, that code must look radically different from code that doesn't".

That isn't to say that there aren't reasonable versions of the above code that don't use async/await. It's just to say that the presence of async operations is (no longer) sufficient reason to force rewriting the code under a different logic-branching paradigm. Put differently, async/await lets us adopt non-blocking, asynchronous flow of control without having to change how we express the flow of logic.

Granted, it's extremely easy to say this now (after async/await has reached stage-4), so I'm not at all pointing fingers at our industry for getting so clever. The fact of the matter is that prior to generators and yield, it was literally impossible to syntactically represent a future task on the event loop within the same immediate function body.

But that was then, and this is now. I find the above code to be expressive enough that it was trivial to write, hopefully it's easy for other developers to reason about, and it's flexible to boot. For example, if I didn't want to force the request to wait for the cache.set() call to finish, I only need to modify a single line. I can introduce new logic branches at anytime without having to do headstands to understand how that affects the downstream promise chain; it just looks like "normal" code.

TL;DR: i dunno, really. I just got carried away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.