6 Reasons Why JavaScript’s Async/Await Blows Promises Away | free online courses

6 Reasons Why JavaScript’s Async/Await Blows Promises Away

6 Reasons Why JavaScript’s Async/Await Blows Promises Away

6 Reasons Why JavaScript’s Async/Await Blows Promises Away

  • by admin
  • Web Development

The async/await 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. You can go through with very helpful JavaScriptTutorial The Full JavaScript & ES6 Tutorial - (including ES7 & React)

 

The good part in async/await

The most important benefit async/await brought to us is the synchronous programming style. Let’s see an example.

// async/await
async getBooksByAuthorWithAwait(authorId) {
  const books = await bookModel.fetchAll();
  return books.filter(b => b.authorId === authorId);
}
// promise
getBooksByAuthorWithPromise(authorId) {
  return bookModel.fetchAll()
    .then(books => books.filter(b => b.authorId === authorId));
}

 

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 getBooksByAuthorWithAwait() and getBooksByAuthorWithPromises() functions in above example. Note that they are not only identical functionally, they also have exactly the same interface!

This means getBooksByAuthorWithAwait() will return a promise if you call it directly.

Well, this is not necessarily a bad thing. Only the name await gives people a feeling that “Oh great this can convert asynchronous functions to synchronous functions” which is actually wrong.

Async/await Pitfalls

So what mistakes may be made when using async/await? Here are some common ones.

Too Sequential

Although await 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.

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.

  1. await bookModel.fetchAll() will wait until fetchAll() returns.
  2. Then await authorModel.fetch(authorId) will be called.

Notice that authorModel.fetch(authorId) does not depend on the result of bookModel.fetchAll() and in fact they can be called in parallel! However by using await here these two calls become sequential and the total execution time will be much longer than the parallel version.

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));
// CORRECT
  const 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 await. In complicated workflow it might be easier to use promises directly.

Error Handling

With promises, an async function have two possible return values: resolved value, and rejected value. And we can use .then() for normal case and .catch() for exceptional case. However with async/await error handling could be tricky.

try…catch

The most standard (and my recommended) way is to use try...catchstatement. When await a call, any rejected value will be thrown as an exception. Here is an example:

class BookModel {
  fetchAll() {
    return new Promise((resolve, reject) => {
      window.setTimeout(() => { reject({'error': 400}) }, 1000);
    });
  }
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
try {
  const books = await bookModel.fetchAll();
} catch (error) {
  console.log(error);    // { "error": 400 }
}

The catched error is exactly the rejected value. After we caught the exception, we have several ways to deal with it:

  • Handle the exception, and return a normal value. (Not using any returnstatement in the catch block is equivalent to using return undefined;and is a normal value as well.)
  • Throw it, if you want the caller to handle it. You can either throw the plain error object directly like throw error;, which allows you to use this async getBooksByAuthorWithAwait() function in a promise chain (i.e. you can still call it like getBooksByAuthorWithAwait().then(...).catch(error => ...)); Or you can wrap the error with Error object, like throw new Error(error) , which will give the full stack trace when this error is displayed in the console.
  • Reject it, like return Promise.reject(error) . This is equivalent to throw error so it is not recommended.

The benefits of using try...catch are:

  • 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 await calls in a single try...catch block to handle errors in one place, if per-step error handling is not necessary.

There is also one flaw in this approach. Since try...catch will catch every exception in the block, some other exceptions which not usually caught by promises will be caught. Think about this example:

class BookModel {
  fetchAll() {
    cb();    // note `cb` is undefined and will result an exception
    return 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 ReferenceError: cb is not defined in the console, in black color. The error was output by console.log() but not the JavaScript itself. Sometimes this could be fatal: If BookModel 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.

Conclusion

In summary, async/await is a cleaner syntax to write asynchronous Javascript code. It enhances readability and flow of your code.

Things to keep in mind while using async/await:

  • Async functions return a promise.
  • Await can only be used inside an async block.
  • Await waits until the function("promise") resolves or rejects.

Its quite easy to learn and use. Enjoy experimenting!!

Tags: JavaScript