Thursday, 13 July, 2017 UTC

Summary

So far in this async series we’ve covered Node.js style callbacks, the Async module, and promises. In this final part of the series, we’ll learn about async functions (a.k.a. async/await). To me, async functions are the most exciting thing to happen to JavaScript since Ajax. Finally, we can read JavaScript code in a synchronous manner while it executes asyncronously as it always has.

Please Note: This post is part of a series on working with database connections using various asynchronous patterns. See that post for more details and links to other options.

Contents:

Async functions overview

try…catch

Async loops

Parallel execution

Async function demo app

Async functions overview

Async functions are a relatively new feature of JavaScript (not specific to Node.js). Support for the feature first landed in Node.js v7.6 via an update to the V8 JavaScript engine. Because async functions rely heavily on Promises, I recommend you read the previous post before continuing.

I like to think of async functions as two parts: async and await. Let’s look at each part in turn.

async

For the longest time, we’ve had the ability to create functions in JavaScript using function statements (must be named) or function expressions (often anonymous).

Just as we saw with promise chaining, if the async function completes without error, then the promise it returns is resolved. If the function returns a value, then that becomes the promise’s value. If an error is thrown and goes unhandled, then the promise is rejected and the error becomes the promise’s value.

Though interesting, returning promises isn’t what makes async functions special. We could, after all, just return promises from regular functions. What makes async functions special is await.

await

The await operator, which is only available inside of an async function, is where the magic happens. It’s like hitting the pause button on your code so that it can wait for a promise to be resolved or rejected before continuing. This is a concept known as a coroutine. Coroutines have been available in JavaScript since generator functions were introduced, but async functions make them much more approachable.

Await does not block the main thread. Instead, the currently running call stack, up to the point of await, is allowed to finish so that other functions in the callback queue can be executed. When the promise is resolved or rejected, the remaining portion of the code is queued for execution. If the promise was resolved, its value is returned. If the promise was rejected, the rejected value is thrown on the main thread.

Here’s a demonstration of await that uses setTimeout to simulate an async API. I’ve added some additional console output to help illustrate what’s happening.

When this script is run in Node.js without an error occurring, the output will look like the following (I’ve added a comment where the two-second delay happens).

before async call
before await undefined
after async call
# 2 second delay
after await 0.22454453163016597

Note that after async call was logged before after await 0.22454453163016597. Only the remaining code in the async function is paused; the remaining synchronous code in call stack will finish.

If an error is thrown, you’ll see the UnhandledPromiseRejectionWarning we covered in the last post. The rejection could be handled with the methods mentioned in that post or using try…catch!

try…catch

In the first post in this series, I explained why try…catch blocks don’t work with asynchronous operations – you can’t catch errors that occur outside of the current call stack. But now that we have async functions, try…catch can be used for asynchronous operations!

Here’s a stripped down version of the previous script that catches errors that occur in the async API and uses a default value instead.

Running this script in Node.js, you should see three numbers printed to the console every two seconds. No third party libraries, no complicated promise chains, just a simple loop. Loops work again, yay!

Parallel execution

Clearly, async functions make it easy to do sequential flows and use standard JavaScript constructs with asynchronous operations. But what about parallel flows? This is where Promise.all and Promise.race come in handy. Because they both return promises, await can work with them like any other promise-based API.

Here’s an example that uses Promise.all to get three random numbers in parallel.

All of the async methods in node-oracledb are overloaded to work with callback functions or promises. If a callback function is not passed in as the last parameter, then a promise will be returned. This version of the index.js uses the await operator with the driver’s promise APIs to create a connection pool and start a promise chain. Although the pool is returned from the call to createPool, it’s not referenced here as the built-in pool cache will be used in employees.js.

The db-config.js file is used in index.js to provide the connection info for the database. This configuration should work with the DB App Dev VM, but it will need to be adjusted for other environments.

This version of the employees module is similar to the promise version in that the getEmployee function was written as a promise-based API – it immediately returns a new promise instance which is asynchronously resolved or rejected. The main difference is that await is used with the driver’s promise APIs to get a connection to the database, use it to execute a query, and then close a connection.

A try…catch…finally block was used to catch errors and ensure the connection was closed either way. To me, this version of the module is the simplest to read of all those in the series and it doesn’t hurt that it has the fewest lines of code as well.

Hopefully, you now have a better grasp on async functions and are as excited as I am about using them.