-
-
Save jmar777/21ace6b8ea9b0cc428fc700faabb77e7 to your computer and use it in GitHub Desktop.
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)); |
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.
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.