DEV Community

loading...
Cover image for Tips For Using Async/Await - Write Better JavaScript!

Tips For Using Async/Await - Write Better JavaScript!

milindsoorya
I'm a passionate hacker wizard
Updated on ・3 min read

Basic Async/Await

Things to note -
There are two parts to using async/await in your code.
First of all, we have the async keyword, which you put in front of a function declaration to turn it into an async function. An async function is a function that knows how to expect the possibility of the await keyword being used to invoke asynchronous code.

const loadData = async () => {
  const url = "https://jsonplaceholder.typicode.com/todos/1";
  const res = await fetch(url);
  const data = await res.json();
  console.log(data);
};

loadData();
Enter fullscreen mode Exit fullscreen mode
// Console output
{
  completed: false,
  id: 1,
  title: "delectus aut autem",
  userId: 1
}
Enter fullscreen mode Exit fullscreen mode

Async/Await with error handling

We can handle errors using a try catch block.

const loadData = async () => {
  try{
      const url = "https://jsonplaceholder.typicode.com/todos/1";
      const res = await fetch(url);
      const data = await res.json();
      console.log(data);
  } catch(err) {
    console.log(err)
  }
};

loadData();
Enter fullscreen mode Exit fullscreen mode

Things to note - The above try-catch will only handle error while fetching data such as wrong syntax, wrong domain names, network error etc. For the situation in which we want to handle an error message from the API response code, we can use res.ok, It will give a Boolean with the value true if the response code is between 200 and 209.

const loadData = async () => {
  try{
      const url = "https://jsonplaceholder.typicode.com/todos/qwe1";
      const res = await fetch(url);
      if(res.ok){ 
        const data = await res.json();
        console.log(data);
      } else {
        console.log(res.status); // 404
      }
  } catch(err) {
    console.log(err)
  }
};

loadData();

// OUTPUT
// 404
Enter fullscreen mode Exit fullscreen mode

Async function returns a promise

This is one of the traits of async functions — their return values are guaranteed to be converted to promises. To handle data returned from an async function we can use a then keyword to get the data.

const loadData = async () => {
  try{
    const url = "https://jsonplaceholder.typicode.com/todos/1";
    const res = await fetch(url);
    const data = await res.json();
    return data
  } catch(err) {
    console.log(err)
  }
};

const data = loadData().then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

💡 PRO TIP :
if you want to use an async-await to handle the returned data you can make use of an IIFE, but it is only available in Node 14.8 or higher.

// use an async IIFE
(async () => {
  const data = await loadData();
  console.log(data);
})();
Enter fullscreen mode Exit fullscreen mode

await only works inside async functions within regular JavaScript code, however it can be used on its own with JavaScript modules.

Promise.all()

Promise.all() comes in handy when we want to call multiple API's. Using a traditional await method we have to wait for each request to be completed before going to the next request. The problem occurs when each request takes some time to complete, this can easily add up and make the experience slower. Using Promise.all() we can call each of these API's in parallel. (it is an oversimplification, for more details checkout this amazing article).

const loadData = async () => {
  try{
    const url1 = "https://jsonplaceholder.typicode.com/todos/1";
    const url2 = "https://jsonplaceholder.typicode.com/todos/2";
    const url3 = "https://jsonplaceholder.typicode.com/todos/3";
    const results = await Promise.all([
      fetch(url1),
      fetch(url2),
      fetch(url3)
    ]);
    const dataPromises = await results.map(result => result.json());
    const finalData = Promise.all(dataPromises);
    return finalData
  } catch(err) {
    console.log(err)
  }
};

const data = loadData().then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode
// Console output
[{
  completed: false,
  id: 1,
  title: "delectus aut autem",
  userId: 1
}, {
  completed: false,
  id: 2,
  title: "quis ut nam facilis et officia qui",
  userId: 1
}, {
  completed: false,
  id: 3,
  title: "fugiat veniam minus",
  userId: 1
}]
Enter fullscreen mode Exit fullscreen mode

👉🏼 checkout my website, milindsoorya.site for more updates and getting in touch. cheers.

Discussion (10)

Collapse
lukeshiru profile image
LUKE知る

You could also just use promises without async/await for functions that are simple as this one:

const loadData = async () => {
    try {
        const url =
            "https://jsonplaceholder.typicode.com/todos/1";
        const res = await fetch(url);
        const data = await res.json();
        return data;
    } catch (error) {
        console.error(error);
    }
};

const data = loadData().then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

You can simply...

const loadData = () =>
    fetch("https://jsonplaceholder.typicode.com/todos/1")
        .then(response => response.json())
        .catch(console.error);

const data = loadData().then(console.log);
Enter fullscreen mode Exit fullscreen mode

async/await is usually useful in scenarios where using promises makes the code hard to read (async iteration, conditional promises, etc), but for stuff like this, using just then makes sense, and keeps it simple.

Collapse
zba profile image
Alexey • Edited

Why do you think it is simpler to use then() ? Usually i write fetch block like

const data = 
     await (await fetch(url)).json()
Enter fullscreen mode Exit fullscreen mode
Collapse
lukeshiru profile image
LUKE知る

I guess is a preference thing. That line you showed is far harder to read for me than the one with the then. I read mine as:

Fetch that url, then parse it as JSON

And I read yours as:

Wait for the JSON parse to be done, but first wait for the fetch to be done.

I mean the fetch one has a more "natural" order, and it doesn't need to make the function async, nor it needs multiple awaits to work.

Thread Thread
nikhilmwarrier profile image
Nikhil M Warrier • Edited

Exactly
That's why I use the then keyword a lot. It makes it super easy for even outsiders to read your code.

Thread Thread
milindsoorya profile image
milindsoorya Author

As long as you don't encounter a promise hell with then keyword, it is indeed okey to use then.

Collapse
jfbrennan profile image
Jordan Brennan

That’s not the same as Luke’s code. You would need to add the try and catch statements.

Thread Thread
zba profile image
Alexey • Edited

Still simple, and why i need them here ? it is probably async function and may be part of solid something, that can handle errors high level.

Collapse
milindsoorya profile image
milindsoorya Author

Yeah you are correct, but I was using a simple example to convey the point. It is pointless to compare these to methods as they have their own use cases.

Collapse
stradivario profile image
Kristiqn Tachev

There is also a useful command for Promise.allSettled since with Promise.all if one promise crashes you will lose the track for all of them.

Using allSettled will give you the result even if one of them crashes. Downside is that with both approaches if you don't do some concurrency using Promise.all and Promise.allSettled will fire up many promises and will make the buffer overflow so it is not memory wise operation.

The way it needs to be is

export type SafePromiseResult<T, E = Error> = [T | null, E | null];

export const safeHandle = async <T, E = Error>(
  promise: Promise<T>,
): Promise<SafePromiseResult<T, E>> => {
  try {
    return [await promise, null];
  } catch (e) {
    return [null, e];
  }
};
Enter fullscreen mode Exit fullscreen mode
import { safeHandle } from './safe-handle';

import concurrent from 'p-limit';

interface MyEntity {
  name: string;
}

const promises: (() => Promise<SafePromiseResult<MyEntity, Error>>)[] = [];
const processRecord = async (entry: MyEntity) => {
  /* My Async logic goes here */
  return entry;
};
const entries = [{ name: 'Oh my god' }];

for (const entry of entries) {
  promises.push(() => safeHandle(processRecord(entry)));
}

const modifiedEntries: MyEntity[] = [];

for (const [entry, error] of await Promise.all(promises.map(concurrent(20)))) {
  if (error) {
    console.log('Error', error);
  } else {
    modifiedEntries.push(entry);
  }
}

console.log(modifiedEntries);
Enter fullscreen mode Exit fullscreen mode

The same can be used with allSettled for node 12

When using concurrent approach the code itself will fire only 20 promises at time.

Collapse
vishalraj82 profile image
Vishal Raj

@milindsoorya You have summed the promise nicely for those looking to get started. For Promise.all, you missed to mention that it will reject, the moment any promise returns failure.
Alternatively, Promise.allSettled lets all the promises run (whether resolve or reject), and finally resolves with the status of each promise as array.