DEV Community

Nico Zerpa (he/him)
Nico Zerpa (he/him)

Posted on • Originally published at nicozerpa.com

Promises or async/await, Which Is Better?

Handling asynchronous tasks in JavaScript has always been a colossal pain in the neck. Back in the day, developers had to deal with problems like callback hell and code that often became hard to read, scale, and maintain.

Nowadays, you have two very powerful tools that let us write asynchronous code: Promises and async/await. But, what's the difference, and when should you use one over the other?

First of all, it's important to point out that it's mostly a matter of opinion. Not everybody will agree with this, and that's OK.

In general, I'd recommend that you use async/await as the preferred option, and use Promises only on specific cases. But you should know how to use Promises anyway.

The biggest pro of async/await is that it's generally easier to read, it almost feels like you're writing regular synchronous code.

When you use promises, every function in the promise chain has its own scope. And that makes it tricky if you need to pass a variable from one method of the chain to another. Using async/await solves this problem because all the asynchronous tasks within the function all use the same scope.

// Async/await version
(async function() {

    const customer = await axios(`https://someapi.co/getCustomerByEmail?email=nico%40nicozerpa.com`);
    const purchases = await axios(`https://someapi.co/getPurchasesByCustomerID/${customer.id}`);

    console.log(`${customer.data.fullName} has purchased ${purchases.data.length} times`);
})();

// Promises version
axios(`https://someapi.co/getCustomerByEmail?email=nico%40nicozerpa.com`)
.then(function (customer) {
    return Promise.all([
        customer,
        axios(`https://someapi.co/getPurchasesByCustomer/${customer.data.id}`)
    ]);
})
.then(function ([customer, purchases]) {
    console.log(`${customer.data.fullName} has purchased ${purchases.data.length} times`);
});
Enter fullscreen mode Exit fullscreen mode

(Note: in the examples, I'm using Axios, a library to make HTTP requests.)

See? The promises version becomes harder to read because it's not as straightforward to pass the variable customer from the first function in the chain to the second.

On the other hand, handling errors is generally easier when you use promises. That's because you can just add a .catch() method at the end of the chain. You can handle it with async/await, using the good ol' try/catch.

Unfortunately, it's sightly more complex, but it means that async/await ends up encouraging developers to avoid catching errors, which is a bad practice.

You can avoid this problem by adding a call to .catch() on the async function if you're using async/await. That works because asynchronous functions return promises. Let's see:

async function printCustomerName() {
    const customer = await axios(`https://someapi.co/getCustomerByEmail?email=nico%40nicozerpa.com`);
    console.log(`The customer name is ${customer.data.fullName}`);
}

printCustomerName().catch(function(err) {
    console.error("An error occurred.");
})
Enter fullscreen mode Exit fullscreen mode

Last but not least, that you can combine both approaches:

(async function() {

    // Using promises and await at the same time, if you want to run two
    // async tasks at the same time, you can do it only with Promise.all
    const [customer, purchases] = await Promise.all([
        axios(`https://someapi.co/getCustomerByID/48`),
        axios(`https://someapi.co/getPurchasesByCustomerID/48`)
    ]);

    console.log(`${customer.data.fullName} has purchased ${purchases.data.length} times`);

})();


(async function() {

    // Using promises and await at the same time again:
    // In this case, the function to convert to JSON is simple
    // to just using Then.
    const customer = await fetch(`https://someapi.co/getCustomerByID/48`)
                            .then(response => response.json());

    console.log(`The customer name is ${customer.data.fullName}`);

})();
Enter fullscreen mode Exit fullscreen mode

To recap, you should mostly use async/await, but in some cases, it's OK to use promises.


Become a Better JavaScript Developer! My newsletter has easy, actionable steps to level up your JavaScript skills, right to your inbox. Click here to subscribe

Latest comments (3)

Collapse
 
peerreynders profile image
peerreynders

you should mostly use async/await, but in some cases, it's OK to use promises.

The Catch 22 is: how does one become competent with promises if one predominantly uses async/await?

async/await accommodates a synchronous mindset leaving everybody in their comfort zone while promises require a shift in thinking - breaking processing into (micro)tasks ("Many of us are using promises without really understanding them", 2015; The Event Loop, 2018).

Exploiting opportunities for concurrent asynchronous execution is possible in async/await with Promise.all() and Promise.allSettled() but a synchronous mindset isn't exactly honed towards recognizing these type of opportunities.

Also before async/await I always found it clearer to use function declarations instead of inline anonymous function expressions.

import axios from 'axios';

const reportInfo = (info) => console.log(infoToMessage(info));
const logError = (error) => console.log(errorToMessage(error));

fetchInfoByTitle('in quibusdam tempore odit est dolorem')
  .then(reportInfo) // 'Post (id: 12) with title "in quibusdam tempore odit est dolorem" has 5 comments.'
  .catch(logError);

function fetchInfoByTitle(title) {
  return axios
    .get('https://jsonplaceholder.typicode.com/posts')
    .then(fetchComments);

  function fetchComments(res) {
    const post = findPostByTitle(title, res.data);
    if (!post) return { title }; // nothing to do

    return axios
      .get(`https://jsonplaceholder.typicode.com/comments?postId=${post.id}`)
      .then(assembleInfo);

    function assembleInfo(res) {
      const comments = res.data;
      return {
        title,
        post,
        comments,
      };
    }
  }
}

function findPostByTitle(title, posts) {
  const findTitle = title.toLowerCase();
  return posts.find((p) => p.title.toLowerCase() === findTitle);
}

function infoToMessage(info) {
  if (!info.post) return `No post with a title of "${info.title}" found.`;

  const { post, comments } = info;
  const result = comments.length !== 1 ? `${comments.length} comments` : `1 comment`;
  return `Post (id: ${post.id}) with title "${post.title}" has ${result}.`;
}

function errorToMessage(error) {
  const url = error?.config?.url;
  return `Error: ${error.message}` + (url ? `; url: ${url}` : '');
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
steelwolf180 profile image
Max Ong Zong Bao

I think in terms of understanding code and learning about either promises vs async/await.

I had a particularly hard time understanding and using async/await. Especially I had to get the result of the return statement when I'm running a async/await function.

For promises wise, I get to understand it better. Since it feels more like a try catch statement.

Collapse
 
lexlohr profile image
Alex Lohr

Async functions are merely semantic sugar for a function that will return a Promise. You can even catch() errors from calling them (which is more succinct than try-catch).