loading...

JavaScript Awaits

kayis profile image K ・3 min read

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);
}

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);
  }
}

"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;
}

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);
}

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);
}

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");
})

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.

Posted on by:

kayis profile

K

@kayis

Taking care of developer relations at Moesif and creating educational content at fllstck.dev

Discussion

markdown guide
 

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
}

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
}
 

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.

 

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.

 

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;
  }
}
 

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