DEV Community

K
K

Posted on

JavaScript Awaits

With ES2017 JavaScript got a feature called async-functions. They are a handy feature to streamline your asynchronous code a bit more.

"But Kay, I just learned that promises are the way to go! Has all my monadic struggling been in vain?!"

You are lucky, because async functions are basically syntactic sugar for promises.

Why?

Well, as with all the syntactic sugar, they clean up your code. They hide a bit of complexity, but you have to see yourself if it's worth it.

For example, a promise chain could look like this:

function f() {
  getServerData()
  .then(parseData)
  .then(filterData)
  .then(relayData)
  .catch(handleError);
}
Enter fullscreen mode Exit fullscreen mode

Writing it with an async-function, it can look like this:

async function f() {
  try {
    const dataString = await getServerData();
    const parsedData = await parseData(dataString);
    const filteredData = await filterData(parsedData);
    await relayData(filteredData);
  }
  catch(e) {
    handleError(e);
  }
}
Enter fullscreen mode Exit fullscreen mode

"Kay, are you insane?! You said it would clean up my code no problem, but look how bad you f*cked it up!"

Yes, you're right, especially if you come from a functional programming background, this must seem like utter madness. This probably wasn't the best example, but it shows one thing: Error handling works like many developers are used to, just try-catch and done. This is because async-functions enable the mix of synchronous and asynchronous code.

Another thing here is, that the awaited functions are simply returning their values now, so you don't have to mess around with the promises anymore, you can simply write your asynchronous code as if it were synchronous. This enables you to use it in other synchronous constructs, like loops or if statements.

async function f() {
  if (await isLoggedIn()) g()
  else h()
}

async function i() {
  const parsedElements = []
  while(let x = await getNextElement()) {
    let y
    try {
      y = await parse(x);
    }
    catch(e) {
      y = handleParseError(e);
    }
    parsedElements.push(y)
  }
  return parsedElements;
}
Enter fullscreen mode Exit fullscreen mode

So synchronous and asynchronous code now plays nicely together in one function and since it's just promises, you can use it with promise based functions out of the box.

function addOne(x) {
  return Promise.resolve(x + 1);
}

async function g() {
  const two = await addOne(1);
}
Enter fullscreen mode Exit fullscreen mode

This also goes the other way, if you got an async-function, you can use it as a promise based function, which it really is, somewhere else. So if you wrote all your code with async-functions and somebody else wants to use it, they aren't forced to use this feature.

async function f() {
  let data
  try {
    data = await parseData(await getData());
  }
  catch(e) {
    data = handleError(e);
  }
  return data
}

function g() {
  f().then(handleNewData);
}
Enter fullscreen mode Exit fullscreen mode

How?

To use this feature you either need

Currently the await keyword is only available inside async-functions, so you can't use it at the global scope of your JavaScript files, you always have to define a function as async.

This is a bit of a restriction, but as I said, async-functions are simply regular functions that happen to return a promise instead of their real value. So you can use them with any framework or library that either expect you to give it a promise or a promise returning function or doesn't do anything with the returned value anyway, which is the case with many functions that want simple callbacks.

const fs = require("fs");
fs.readFile('README.md', async function (e, data) {
  if (e) return console.error(e);
  if (await validateOnServer(data)) console.log("File OK");
})
Enter fullscreen mode Exit fullscreen mode

Conclusion

I think async-functions are a nice way to integrate sync and async code if you prefer imperative programming. If you already understood promises you should feel right at home with it.

For functional programming it may be a step back to hide promises, but functional programmers probably left promises behind for observables years ago.

Oldest comments (5)

Collapse
 
ssalka profile image
Steven Salka

Great post! You can also use async-await syntax with TypeScript

Collapse
 
amorgaut profile image
Alexandre Morgaut • Edited

I would just add for the last example that Node 8 also includes a promise utility you can use to write:

const { promisify } = require('util');
const fs = require('fs');
const readFileAsync = promisify(fs.readFile);

async function f() {
  try {
    const data = await readFileAsync('README.md');
    if (await validateOnServer(data)) {
      console.log("File OK");
    }
  } catch (e) {
    console.error(e);
    return;
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
danielescoz profile image
Daniel Escoz

First, I liked the "everything is asynchronous" way of working of Node.
Then, I loved Promises because it made async code a first-class citizen.
Now, I'm just completely and absolutely sold on async/await and won't change it for anything.

Seriously, with Promises I could maybe accept they were glorified callbacks in most situations. They're really not, but well, ok, you like callbacks, that's fine. But with await, oh my god, that's another world. I used to write functions to emulate while loops with promises, but now? now you can write loops with promises!! It's awesome.

So yeah, thank you for this article, more people need to know the goodies of async/await.

Collapse
 
kayis profile image
K

I found observables more sound, because they make everything work like an array on steroids.

But I have to admit, abstracting async behavior behind an array-like idea is a bit much to swallow for the regular dev. Making it accessible via constructs like loops is much easier to grasp.

Collapse
 
godspeedelbow profile image
Eelke Boezeman

It happens often that an asynchronous function takes more than one (earlier computed) asynchronous value. With async/await, all the async function calls are done within the same function scope, exposing the values within that scope, and therefore composing those values with other asynchronous functions requires much less boiler plate.

async function f() {
    const dataString = await getServerData();
    const parsedData = await parseData(dataString);
    const filteredData = await filterData(parsedData, dataString); // <-- scoped access to dataString
}
Enter fullscreen mode Exit fullscreen mode

Using a promises-only approach, or callbacks only, or something like the async library will always result in a lot more boiler plate to have access to dataString, e.g. promises-only:

function f() {
  getServerData()
  .then(dataString => [dataString, parseData(dataString)]), // <-- pass dataString along
  .then([dataString, parsedData] => filterData(parsedData, dataString)) // <-- so you can access it here
}
Enter fullscreen mode Exit fullscreen mode