Async await выглядит так хорошо, потому что позволяет избежать ада обратных вызовов или пирамиды гибели:

путем написания асинхронного кода в чистом однострочном формате.

async function() {
  const a = await step1();
  const b = await step1(a);
  return a + b;
}

Но обработка ошибок может оказаться непростой задачей. Потому что вы можете получить несколько try catch, поэтому один лайнер расширяется как минимум до пяти строк кода.

async function() {
  let a, b;
  try {
    a = await step1();
  } catch(error) {
    handleError(error)
  }

  try {
    b = await step1(a);
  } catch(error) {
    handleError(error)
  }
  return a + b;
}

Чтобы избежать написания множественных асинхронных ожиданий try-catch в функции, лучшим вариантом является создание функции для переноса каждой попытки catch.

Первый результат обещания возвращает массив, где первый элемент — это данные, а второй — ошибка. И если есть ошибка, то данные равны нулю, и ошибка определена.

async function promiseStep1() {
  try {
     const data = await step1();
     return [data, null];
   } catch(error) {
     return [null, error];
   }
}

Теперь, когда вы вызываете эту функцию в своем коде, вы можете деструктурировать ее, чтобы получить чистый однострочный код с обработкой ошибок:

async function main() {
    const [data, error] = await promiseStep1();
    const [data2, error2] = await promiseStep2();
}