6 причин, чому async/await краще за проміси

9 хв. читання

Node підтримує async/await починаючи з версії 7.6, а Babel вміє транспілювати його в ES5 (хоча й не дуже ефективно). Якщо ви ще не знаєте що це таке і далі плануєте користуватися промісами, то саме час це виправити!

Довідка

Невеличкий вступ, якщо ви ще не чули про async/await:

  • Async/await — новий спосіб писати асинхронний код, до нього були проміси та колбеки.
  • Сам async/await працює поверх промісів, тому не може бути використаний з колбеками.
  • Як і проміси, ця конструкція не блокує виконання.
  • Async/await робить ваш асинхронний код більш схожим на синхронний, що полегшує життя розробникам.

Синтакс

Уявимо, що в вас є функція getJSON, яка повертає проміс, який згодом виконується з JSON-об'єктом. Ми просто хочемо вивести в консоль цей об'єкт та повернути "done".

Ось так це робиться з промісами:

const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })

makeRequest()

А ось так з async/await:

const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest()

Тут є декілька важливих відмінностей:

  1. Перед нашою функцією тепер стоїть ключове слово async. Використовувати await можна лише в async-функціях. Всі async-функції повертають проміс, що виконується з тим значенням, яке ви передаєте оператору return (в випадку вище — done).

  2. Вищесказане означає, що ми не можемо використовувати await в верхньому рівні нашого коду, так як він не є async-функцією.

// Це не спрацює на верхньому рівні
await makeRequest()

// А це спрацює
makeRequest().then((result) => {
  // якийсь код
})
  1. await getJSON() означає, що console.log почекає поки виконається getJSON() і лише потім виведе результат виконання.

І в чому переваги?

1. Коротше та чистіше

Навіть на невеличких прикладах вище це видно. Нам не потрібно писати .then, створювати анонімну функцію, створювати змінну data. Також ми уникнули вкладеного коду. Так дрібнички дуже важливі в великих, і малих проектах.

2. Обробка помилок

Нарешті! Тепер ми можемо відловлювати помилки як в синхронному, так і в асинхронному коді за допомогою однієї конструкції: старого доброго try/catch. В прикладі з промісами try/catch не перехопить помилку, якщо JSON.parse не виконається, адже цей код знаходиться всередині промісу і для цього слід використовувати метод .catch і дублювати наш код обробки помилок, який, сподіваюсь, куди складніший ніж console.log.

const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // тут може виникнути помилка
        const data = JSON.parse(result)
        console.log(data)
      })
      // Розкоментуйте цей блок щоб відловлювати асинхронні помилки
      // .catch((err) => {
      //   console.log(err)
      // })
  } catch (err) {
    console.log(err)
  }
}

А тепер подивіться на еквівалентний код з async/await:

const makeRequest = async () => {
  try {
    // тут може виникнути помилка
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

3. Виразність

Припустимо, вам потрібна функція, що робить запит, а потім на основі отриманих даних або повертає їх, або робить ще один запит.

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

Від одного погляду на це стає боляче. Дуже просто тут заплутатися (ще б пак, тут аж 6 рівнів вкладення). Цей приклад стає куди виразніше з async/await:


const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

4. Проміжні значення

Впевнений, що в вас була ситуація коли ви викликаєте promise1, потім використовуєте дані, які він повернув в promise2, а потім використовуєте дані з двох промісів в promise3. І код ваш виглядав приблизно так:


const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // робить щось
      return promise2(value1)
        .then(value2 => {
          // робить щось         
          return promise3(value1, value2)
        })
    })
}

Якщо promise3 не потребує value1, то досить просто буде зробити цей код "пласкішим". Або якщо ви з тих людей, що не можуть миритися з такими кодом, ви використаєте Promise.all щоб запобігти вкладенню. Якось так:


const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // робить щось
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {
      // робить щось          
      return promise3(value1, value2)
    })
}

Такий підхід жертвує семантикою на користь читабельності. З async/await зробити це просто. Це змусить вас здивуватися скільки часу й сил ви витратили щоб зробити проміси не такими огидними.

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

5. Стеки помилок

Уявіть собі код, який виконує декілька промісів в ланцюжку, і десь виникає помилка.


const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // Вивід:
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })

Зі стеку помилок, що буде повернено неможливо зрозуміти де саме сталася помилка. Більше того, він вводить в оману: назва функції в ньому — callAPromise, хоча вона зовсім не винна в цій помилці (ім'я файлу та номер рядка все ще корисні тут).

Стек помилок з коду, що використовує async/await куди зрозуміліший:

const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // Виведе
    // Error: oops at makeRequest (index.js:7:9)
  })

Це не так важливо коли помилка сталася при розробці в локальному середовищі. Але дуже корисно знати, що помилка в функції makeRequest а не в then.then.then, коли вам приходить лог помилок з продакшн-серверу.

6. Дебаг

Кіллер-фіча async/await – це те, що код з його використання набагато легше дебажити. Дебажити проміси завжди було боляче, і на це є дві причини.

  1. Ви не можете встановити точку переривання в анонімній функції, що повертає вираз. Ось спробуйте встановити брейкпоінт десь тут:

6 причин, чому async/await краще за проміси

  1. Якщо ви встановили точку переривання всередині .then, то не зможете використовувати так фічі як step-over, так як він проходить лише по синхронному коду.

З async/await таких проблем немає, бо вам просто не потрібні анонімні функції і ви можете проходити по await-викликах, наче це синхронний код.

6 причин, чому async/await краще за проміси

Висновок

Async/await, напевно, найбільш революційна функція, яка була додана до JavaScript за останні роки. Можливо вам незвично і вас лишились деякі скептичні зауваження.

  • Це робить асинхронний код менш очевидним. Ви звикли розрізняти асинхронний код за колбеками або .then, і вам знадобиться декілька тижнів щоб призвичаїтися. В C# така конструкція присутня давно і люди, що її використовують — підтвердять вам, що це лише тимчасові незручності.

  • Node 7 – не LTS-реліз. Але ось-ось має вийти восьма версія, яка матиме довгий термін підтримки, і портувати свій код на неї буде досить просто.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація