One of the most powerful new features in Node.js 8 is the introduction of async functions. Part of the 2017 ECMAScript draft specification, async functions allow you reap the benefits of Promises for simplifying code flow while structuring your code in a more natural way.
The key is that unlike a regular function, which always runs to completion, an async function can be intentionally suspended midexecution to wait on the resolution of a Promise. Note that this does not violate the central maxim that JavaScript is single-threaded. It’s not that some other code will preempt your async function, but rather that you choose to unblock the event loop to await a Promise.
An example should clarify. Consider this contrived function that returns a Promise.
| | const delay = (timeout = 0, success = true) => { |
| | const promise = new Promise((resolve, reject) => { |
| | setTimeout(() => { |
| | if (success) { |
| | resolve(`RESOLVED after ${timeout} ms.`); |
| | } else { |
| | reject(`REJECTED after ${timeout} ms.`); |
| | } |
| | }, timeout); |
| | }); |
| | return promise; |
| | }; |
The delay function takes two arguments, a timeout in milliseconds and a success Boolean value that indicates whether the returned Promise should be resolved (true) or rejected (false) after the specified amount of time. Using the delay function is pretty straightforward—you call its .then() and .catch() methods to assign callback handlers. Here’s an example:
| | const useDelay = () => { |
| | delay(500, true) |
| | .then(msg => console.log(msg)) // Logs "RESOLVED after 500 ms." |
| | .catch(err => console.log(err)); // Never called. |
| | }; |
The useDelay function invokes the delay to get a Promise that’s scheduled to be resolved after 500 milliseconds. Whether the Promise is resolved or rejected, the result is logged to the console.
Now, let’s see what useDelay would look like as an async function.
| | const useDelay = async () => { |
| | try { |
| | const msg = await delay(500, true); |
| | console.log(msg); // Logs "RESOLVED after 500 ms." |
| | } catch (err) { |
| | console.log(err); // Never called. |
| | } |
| | }; |
First, notice the addition of the async keyword in the function declaration. This signals that the function can use the await keyword to suspend while resolving a Promise.
Next, check out the await keyword inside the try{} block, right before the call to delay. Inside of an async function, await suspends execution until the Promise has been settled. If the Promise is resolved, then the await expression evaluates to the resolved value and the async function picks up where it left off.
On the other hand, if the Promise is rejected, then the rejection value gets thrown as an exception. In this case, we use the catch{} block to handle it.
Using async functions together with Promises presents a consistent, synchronous coding style for both synchronous and asynchronous operations. To practice, we’ll use async functions for the remaining bundle APIs that we’ll be adding.