The introduced by ES7 is a fantastic improvement in asynchronous programming with JavaScript. It provided an option of using synchronous style code to access resoruces asynchronously, without blocking the main thread. However it is a bit tricky to use it well. In this article we will explore async/await from different perspectives, and will show how to use them correctly and effectively. async/await The good part in async/await The most important benefit brought to us is the synchronous programming style. Let’s see an example. async/await // async/awaitasync getBooksByAuthorWithAwait(authorId) {const books = await bookModel.fetchAll();return books.filter(b => b.authorId === authorId);} // promisegetBooksByAuthorWithPromise(authorId) {return bookModel.fetchAll().then(books => books.filter(b => b.authorId === authorId));} It is obvious that the version is way easier understanding than the promise version. If you ignore the keyword, the code just looks like any other synchronous languages such as Python. async/await await And the sweet spot is not only readability. has native browser support. As of today, all the mainstream browsers have full support to async functions. async/await All mainstream browsers support Async functions. (Source: ) https://caniuse.com/ Native support means you don’t have to the code. More importantly, it facilitates debugging. When you set a breakpoint at the function entry point and step over the line, you will see the debugger halt for a short while while doing its job, then it moves to the next line! This is much easier than the promise case, in which you have to setup another breakpoint on the line. transpile await bookModel.fetchAll() .filter .filter Debugging async function. Debugger will wait at the await line and move to the next on resolved. Another less obvious benefit is the keyword. It declares that the function return value is guaranteed to be a promise, so that callers can call or safely. Think about this case (bad practice!): async getBooksByAuthorWithAwait() getBooksByAuthorWithAwait().then(...) await getBooksByAuthorWithAwait() getBooksByAuthorWithPromise(authorId) {if (!authorId) {return null;}return bookModel.fetchAll().then(books => books.filter(b => b.authorId === authorId));} In above code, may return a promise (normal case) or a value (exceptional case), in which case caller cannot call safely. With declaration, it becomes impossible for this kind of code. getBooksByAuthorWithPromise null .then() async Async/await Could Be Misleading Some articles compare async/await with Promise and claim it is the next generation in the evolution of JavaScript asynchronous programming, which I respectfully disagree. Async/await IS an improvement, but it is no more than a syntactic sugar, which will not change our programming style completely. Essentially, async functions are still promises. You have to understand promises before you can use async functions correctly, and even worse, most of the time you need to use promises along with async functions. Consider the and functions in above example. Note that they are not only identical functionally, they also have exactly the same interface! getBooksByAuthorWithAwait() getBooksByAuthorWithPromises() This means will return a promise if you call it directly. getBooksByAuthorWithAwait() Well, this is not necessarily a bad thing. Only the name gives people a feeling that “Oh great this can convert asynchronous functions to synchronous functions” which is actually wrong. await Async/await Pitfalls So what mistakes may be made when using ? Here are some common ones. async/await Too Sequential Although can make your code look like synchronous, keep in mind that they are still asynchronous and care must be taken to avoid being too sequential. await async getBooksAndAuthor(authorId) {const books = await bookModel.fetchAll();const author = await authorModel.fetch(authorId);return {author,books: books.filter(book => book.authorId === authorId),};} This code looks logically correct. However this is wrong. will wait until returns. await bookModel.fetchAll() fetchAll() Then will be called. await authorModel.fetch(authorId) Notice that does not depend on the result of and in fact they can be called in parallel! However by using here these two calls become sequential and the total execution time will be much longer than the parallel version. authorModel.fetch(authorId) bookModel.fetchAll() await Here is the correct way: async getBooksAndAuthor(authorId) {const bookPromise = bookModel.fetchAll();const authorPromise = authorModel.fetch(authorId);const book = await bookPromise;const author = await authorPromise;return {author,books: books.filter(book => book.authorId === authorId),};} Or even worse, if you want to fetch a list of items one by one, you have to rely on promises: async getAuthors(authorIds) {// WRONG, this will cause sequential calls// const authors = _.map(// authorIds,// id => await authorModel.fetch(id)); // CORRECTconst promises = _.map(authorIds, id => authorModel.fetch(id));const authors = await Promise.all(promises);} In short, you still need to think about the workflows asynchronously, then try to write code synchronously with . In complicated workflow it might be easier to use promises directly. await Error Handling With promises, an async function have two possible return values: resolved value, and rejected value. And we can use for normal case and for exceptional case. However with error handling could be tricky. .then() .catch() async/await try…catch The most standard (and my recommended) way is to use statement. When a call, any rejected value will be thrown as an exception. Here is an example: try...catch await class BookModel {fetchAll() {return new Promise((resolve, reject) => {window.setTimeout(() => { reject({'error': 400}) }, 1000);});}} // async/awaitasync getBooksByAuthorWithAwait(authorId) {try {const books = await bookModel.fetchAll();} catch (error) {console.log(error); // { "error": 400 }} The ed error is exactly the rejected value. After we caught the exception, we have several ways to deal with it: catch Handle the exception, and return a normal value. (Not using any statement in the block is equivalent to using and is a normal value as well.) return catch return undefined; Throw it, if you want the caller to handle it. You can either throw the plain error object directly like , which allows you to use this function in a promise chain (i.e. you can still call it like ); Or you can wrap the error with object, like , which will give the full stack trace when this error is displayed in the console. throw error; async getBooksByAuthorWithAwait() getBooksByAuthorWithAwait().then(...).catch(error => ...) Error throw new Error(error) Reject it, like . This is equivalent to so it is not recommended. return Promise.reject(error) throw error The benefits of using are: try...catch Simple, traditional. As long as you have experience of other languages such as Java or C++, you won’t have any difficulty understanding this. You can still wrap multiple calls in a single block to handle errors in one place, if per-step error handling is not necessary. await try...catch There is also one flaw in this approach. Since will catch every exception in the block, some other exceptions which not usually caught by promises will be caught. Think about this example: try...catch class BookModel {fetchAll() {cb(); // note `cb` is undefined and will result an exceptionreturn fetch('/books');}} try {bookModel.fetchAll();} catch(error) {console.log(error); // This will print "cb is not defined"} Run this code an you will get an error in the console, in black color. The error was output by but not the JavaScript itself. Sometimes this could be fatal: If is enclosed deeply in a series of function calls and one of the call swallows the error, then it will be extremely hard to find an undefined error like this. ReferenceError: cb is not defined console.log() BookModel Making functions return both value Another way for error handling is inspired by Go language. It allows async function to return both the error and the result. See this blog post for the detail: _ES7 Async/await allows us as developers to write asynchronous JS code that look synchronous. In current JS version we…_blog.grossman.io How to write async await without try-catch blocks in Javascript In short, you can use async function like this: [err, user] = await to(UserModel.findById(1)); Personally I don’t like this approach since it brings Go style into JavaScript which feels unnatural, but in some cases this might be quite useful. Using .catch The final approach we will introduce here is to continue using . .catch() Recall the functionality of : It will wait for a promise to complete its job. Also please recall that will return a promise too! So we can write error handling like this: await promise.catch() // books === undefined if error happens,// since nothing returned in the catch statementlet books = await bookModel.fetchAll().catch((error) => { console.log(error); }); There are two minor issues in this approach: It is a mixture of promises and async functions. You still need to understand how promises work to read it. Error handling comes before normal path, which is not intuitive. Conclusion The keywords introduced by ES7 is definitely an improvement to JavaScript asynchronous programming. It can make code easier to read and debug. However in order to use them correctly, one must completely understand promises, since they are no more than syntactic sugar, and the underlying technique is still promises. async/await Hope this post can give you some ideas about themselves, and can help you prevent some common mistakes. Thanks for your reading, and please clap for me if you like this post. async/await