A Short Guide to Async/Await in JavaScript

Async/await is a modern syntax for working with asynchronous code, specifically promises.

It enables you to wait for the result of a promise inside an asynchronous function without the need to use then() and catch() methods.

To create an asynchronous function, simply include the async keyword before a regularly defined function:

async function add(x, y) {
  const res = x + y;
  return res;
};

const res = add(2, 3);

console.log(res); // Promise {<fulfilled>: 5}

add(2,3).then(res => console.log(res)) // 5

Unlike a regular function, an async function always returns a promise object rather than a simple value.

This means that another process (maybe another async function) can wait upon its return value.

To wait for promises in an async function, use the await keyword before anything that returns a promise:

// Makes and waits for fetch and parsing:
async function getData(url) {
  const res = await fetch(url);
  const data = await res.json();
  return data;
};

// Waits for the return value:
async function logData() {
  const res = await getData('https://jsonplaceholder.typicode.com/posts');
  console.log(res);
};

logData();

If the getData() function were written without async/await, it would require more code and nesting:

function getData(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
    .then(res => res.json())
    .then(data => resolve(url))
  })
};

Error can be handled with async/await using try and catch:

async function logData() {
  try {
    const res = await getData('https://jsonplaceholder.typicode.com/posts');
    console.log(res); 
  } catch(err) {
    throw new Error(err); // Runs if error occurs when running try code
  }
};

Some really awesome features of async/await are that you can use it wait for multiple asynchronous processes to complete before continuing:

// Define URLs to fetch:
const urls = [
    'https://jsonplaceholder.typicode.com/posts',
    'https://jsonplaceholder.typicode.com/comments',
    'https://jsonplaceholder.typicode.com/photos'
];

// Use map to create a new array of promises from async callback function:
const map = urls.map(async (url) => {
    const res = await fetch(url);
    const data = await res.json();
    return data;
})

// Wait for promises array:
async function logData() {
    try {
        const res = await Promise.allSettled(map);
    } catch(err) {
        console.log(err);
    }
};

And even use it in a loop to complete promises one after another:

const inputEl = document.querySelector('input[type="file"]');

// Wait for file(s) selected and then fire async function:
inputEl.addEventListener('change', async () => {

  for (file of inputEl.files) {
    const result = await readAFile(file);
    console.log(result); // Logs when current reading is complete
  }

})

function readAFile(file) {
  return new Promise((resolve) => {
    const fr = new FileReader();
    fr.addEventListener('load', () => {
      resolve(fr.result);
    });
    fr.readAsDataURL(file);
  })
}

If you want to use async/await to wait for a callback function, you need to ‘promisify’ the result of the callback function.

Though an async function returns a promise, its return value cannot be specified inside a nested function’s scope.

So instead you need to wrap a callback function and the parent function that’s calling it inside a promise created using the Promise constructor. This allows you to pass the result into the resolve parameter, which can be called in nested scope:

function promiseTimeout(waitInMs) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("value"); // Callback result passed in to resolve()
    },1000);
  });
}

async function() {
  const result = await promiseTimeout(1000);
  console.log(result);
}

The readAFile() function from previously is another example of this.