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()
Тут є декілька важливих відмінностей:
-
Перед нашою функцією тепер стоїть ключове слово
async
. Використовуватиawait
можна лише вasync
-функціях. Всіasync
-функції повертають проміс, що виконується з тим значенням, яке ви передаєте операторуreturn
(в випадку вище —done
). -
Вищесказане означає, що ми не можемо використовувати
await
в верхньому рівні нашого коду, так як він не єasync
-функцією.
// Це не спрацює на верхньому рівні
await makeRequest()
// А це спрацює
makeRequest().then((result) => {
// якийсь код
})
-
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 – це те, що код з його використання набагато легше дебажити. Дебажити проміси завжди було боляче, і на це є дві причини.
- Ви не можете встановити точку переривання в анонімній функції, що повертає вираз. Ось спробуйте встановити брейкпоінт десь тут:
- Якщо ви встановили точку переривання всередині
.then
, то не зможете використовувати так фічі якstep-over
, так як він проходить лише по синхронному коду.
З async/await таких проблем немає, бо вам просто не потрібні анонімні функції і ви можете проходити по await-викликах, наче це синхронний код.
Висновок
Async/await, напевно, найбільш революційна функція, яка була додана до JavaScript за останні роки. Можливо вам незвично і вас лишились деякі скептичні зауваження.
-
Це робить асинхронний код менш очевидним. Ви звикли розрізняти асинхронний код за колбеками або
.then
, і вам знадобиться декілька тижнів щоб призвичаїтися. В C# така конструкція присутня давно і люди, що її використовують — підтвердять вам, що це лише тимчасові незручності. -
Node 7 – не LTS-реліз. Але ось-ось має вийти восьма версія, яка матиме довгий термін підтримки, і портувати свій код на неї буде досить просто.
Ще немає коментарів