Update: the text below is only applicable for babel code running under older Node.js versions (v6.x and prior). Starting with Node.js v7.x, unhandled promise rejections are now automatically shown in console. In future versions of Node.js unhandled promise rejections will cause code execution to exit with non-zero exit code.
This is a mini-post explaining a small (not so) hidden danger of using async/await in your ES7/babel code.
Async/await is currently a stage-3 proposal for ES7 and already has been implemented by some engines (e.g. Chakra from Microsoft). It’s a pretty neat little addition that helps at writing way less code and being much more concise while still staying asynchronous.
And it happens so that I just read a story called “The only bad thing about ES7 async/await” which pointed out that the “only bad thing” about async/await was the slowdown related to execution of two async operations one after another. I want to talk a bit about why I think that’s not quite correct. And what’s the real “bad thing” (probably not the only one though) when using async/await in my opinion.
First, I want to note that in my opinion mentioned sequential code execution is not a shortcoming at all — you don’t always need to run async operations in parallel. For example when second function call relies on results from the first one, you actually need to wait. Here’s an example from my current project showing password reset for a user:
On other hand, when you actually want to run async operations in parallel, you can simply wrap them into Promise.all() as the author of the post suggests. The code will look something like this:
The thing is, there’s still a huge problem with all of the snippets above. Have you noticed it? If you are familiar with promises — you probably did. The problem is — all the errors in those snippets would disappear without a trace (upd: Node.js v6.6+ now reports unhandled rejections by default).
That happens because under the hood we’re still using promises. Have you noticed any .catch() blocks anywhere? Nope. That means errors won’t appear at all. So, how do we catch them? So far I’d found three options that can be applied in different scenarios.
Option 1. Use try/catch construct. It’s quite simple if you have only one await operation, your code will look something like this:
This approach works well when you have only one await statement. Once you start having more — it’ll be really hard to call your code “concise” and that sweet async/await won’t really help you much.
Option 2. But what to do when you do have many await statements? The answer comes from Promise land — just use .catch() after async function execution.
For example, in my current project when I use async/await with express.js, I wrap all the handlers with a special wrapper function that catches all errors and sends them back to the client with needed error code (500 or whatever is needed depending on the error). Here’s a snippet showing wrapper, route handler and route setup:
Option 3. But what if you forgot to catch errors? You still want to see them, right? How do you catch all uncaught errors from Promises? Sadly, you can only do that with default promise implementation in latest node versions. But if we take a better promise implementation, like say Bluebird, we can setup a global event handler that will catch all unhandled promise errors. The code will look something like this:
If you want to see more async/await code examples “in the wild”, have a look at my current Exynize REST API project that utilizes them quite heavily.
That’s about it. Keep coding and make sure to catch all the errors in your async code! :)