Sinking your teeth into `async`/`await`

This is a bit of a followup to an article I wrote about a couple years ago on JavaScript synchronization patterns: https://medium.com/@ExplosionPills/javascript-synchronization-patterns-ec8c05ac05be

async/await is not really new to JavaScript, and it is certainly not new to development. At least, I know it has been a part of C# for a very long time — if not the beginning.

Even so, I’m not sure that async/await is very widely adopted yet. It just received first class support in browsers and Node.js (with version 8) this year: http://caniuse.com/#search=async%20functions, but even Edge supports it.

This article will explain what it is and give examples of how to use it.

async/await is syntax that can be used for code synchronization, i.e. handling asynchronous operations and performing actions when they are done. The mechanism async/await uses to do this is the Promise. If you are very familiar with Promises in JS, you will have very little difficulty adopting async/await since it is mostly a new way of writing the Promise syntax.

I generally prefer to write code using async/await as it’s usually easier to both write and read than promise chaining. You still get the advantages of Promises: single execution chain, simpler error handling, and cleaner code with naturally less rightward drift. I think async/await is even cleaner, and you get another added benefit of simplifying passing values between links in the promise chain that aren’t touching (example later).

What does `async` do?

async is a keyword that turns a function into an AsyncFunction. Use async before a function declaration to make it such. You can do this with any kind of function declaration — just put the word async in front of it. Note: you can still use async as a variable name.

async function foo() {
return barAsync();
}
const bar = async () => {
return bazAsync();
}
// variable named `async` that contains an AsyncFunction
const async = async value => {}
// using `async` in a callback
Observable.interval(1000).concatMap(async sec => {
await delay(sec * 1000);
return sec;
});
// `async` function IIFE
(async () => {})()

Essentially just put async right in front of anywhere else where you would declare a function and that makes it an AsyncFunction.

AsyncFunctions always return a Promise

This is very important to keep in mind. You can’t make every function async because it changes how the function works. Just remember that any async function always returns a promise no matter what. Even the empty function examples above like async () => {} will return a Promise. You need to anticipate this when working with Async function calls.

Using async when you want to use the await keyword

I’ll discuss await next. You need a function to be async to use it, though.

What does `await` do?

If statement after await is a Promise, the next statement won’t be executed until that promise resolves. It might not be totally accurate, but you can think of await as another way of writing .then.

let val;
someAsync().then(response => val = response);
// with await
const val = await someAsync();

In other words, await waits for the promise of the expression you pass to resolve and then unwraps its value. Note that you’re not required to pass an asynchronous expression to await. You can do const foo = await "bar" and foo will be set to the string "bar". This allows you to use await even if you can’t know ahead of time whether the expression you’re awaiting is asynchronous or not.

For example, say you want to read JSON from a url and write it to a file:

const readAndWriteOld = (url, fileName) => {
fetch(url)
.then(data => data.json())
.then(json => writeFileAsync(fileName, url))
.then(() => {
console.log("Finished");
})
}
const readAndWrite = async (url, fileName) => {
const data = await fetch(url);
const json = await data.json();
await writeFileAsync(fileName, json);
console.log("Finished");
};

fetch and data.json() both return promises and are asynchronous operations. Even though these statements appear to execute synchronously, when you use await it will actually wait for the promise to resolve before executing the next statement.

Note that readAndWrite() still returns a Promise, however its value will be empty. We could use return writeFileAsync instead if we wanted have it return a promise containing the value of the write file operation.

await is an operator and can be used like any operator. Unfortunately, MDN does not define its operator precedence, so use parens for grouping if you are unsure.

async function awaitPlayground(num) {
// use in assignment
let obj = { a: 1 + num, b: 1 + await getNumAsync() };
  // use in conditionals
if (await conditionAsync())
obj = await doSomethingCool(obj);
  // use as function arguments
callMe(await maybe(obj));
}

The above written using .then with Callbacks would look like

let obj;
getNumAsync().then(val => {
obj = { a: 1 + num, 1 + val };
return conditionAsync()
}).then(condition => {
if (condition)
return doSomethingCool(obj).then(response => obj = response)
}).then(() => maybe(obj))
.then(maybed => {
callMe(maybed);
});

I think that this is a lot less natural and more difficult to reason about. In the first example, everything flows as expected. Branching promise chains can be done with typical conditional branches, and we have access to variables that were set in the middle of the chain.

Occasionally, using .then is cleaner and will work just fine, but the option is definitely still there for you. I find this to be the case for very simple chains that have specific functionality for one .then and one .catch. Speaking of…

What about `.catch`?

With async/await, you can use normal try/catch syntax for circumstances where you would use .catch. Building off of our example above:

const readAndWrite = async (url, fileName) => {
try {
const data = await fetch(url);
const json = await data.json();
await writeFileAsync(fileName, json);
console.log("Finished");
}
catch (err) {
console.log(err.message);
}
};

Any error that occurs in the chain above will propagate to the catch block, e.g. if JSON cannot be parse or if the file can’t be written to.

You can also nest try/catch if you want to catch an error for a specific await or group.

Finally, using try/catch is not a requirement. One nice thing about async is that if an uncaught error occurs in the function, the promise it returns will be rejected and you can handle that externally. In fact, you can still catch messages and propagate them using throw

const readAndWrite = async (url, fileName) => {
try { ... }
catch (err) {
throw new ReadAndWriteError(err);
}
};

You can handle this with readAndWrite(url, filename).catch(handleError) for example, or it could even be called inside another async function where you could use try/catch .

Simultaneous Async Operations

Since await works on promises, using multiple promises at the same time is easy. Just wrap them in Promise.all

async storeInfo(username, password) {
await Promise.all([
storage.set('username', username),
secureStorage.set('password', password),
]);
}

In fact, you can use destructuring when working with await … just think of it as any other operator.

const [username, password] = await Promise.all([
storage.get('username'),
secureStorage.get('password'),
]);

More Examples

Writing code using async/await may take a little bit of practice, but I think it is very easy to adopt. It’s even easier to write than typical Promise chaining, although I think it requires an understanding of the Promise chain, or at least asynchronous code.

The best way to do this is to take some code that is using promise chains and rewrite it to use async/await … then test it to see that it works the same. Anything you can do with a promise chain you can do with async/await, so if you’re already doing a lot of work with promises, you can take any of that code and write it using async/await now. You can even test it in Node 8 or most browsers first class.

Here are a few more examples:

I think that this final example showcases the true power of async/await. Keeping everything in one promise chain can become daunting and confusing. You’re already starting with a return. You need access to values created in one promise chain link in a separate link (in this case secureStorage that gets created needs to be closed). There is no great way to do this with promise chains outside of a variable in the an outer scope, or passing it through with the return value of another promise in the chain. With await you can easily access these values since they are created in the same scope.

Conclusion

async/await is an attempt at friendlier syntax for writing synchronized code. You get all of the benefits of promises, but you can write your code as if it were synchronous thanks to the friendly little await keyword. You just have to keep in mind what your async operations are.

Just remember that async/await is all about promises and that async returns a Promise!