Async/Await в javascript. Погляд з боку



Останнім часом все більше моїх друзів, колег і людей зі спільноти говорять про роботу з асинхронними функціями і зокрема про використання async/await на своїх проектах. Я вирішив розібратися для себе, що це за звір і чи варто його використовуватися при розробці бойових проектів.

Перше що хочеться розвіяти, це поширена помилка про те, що async/await — це фіча ES7.

На мою думку, використання термінів ES6 і ES7 саме по собі не дуже вірне і може ввести розробників в оману. Після вдалого релізу специфікації ES2015, званої ES6, у багатьох людей склалося хибна думка, що все в неї не увійшло і заполифилено через babel — це фічі ES7. Це не так. Ось список того що з'явиться з релізом специфікації ES2016. Як бачите він не такий великий і async/await в ньому ніяк не значиться.

Я хочу, щоб ми говорили правильно. І кажучи про тієї чи іншої фиче, посилалися на конкретну специфікацію в рамках якої вона описана і реалізована, а не міфічні ES6, ES7… ESN.

Рухаємося далі. Так що ж таке async/await простими словами?
Говорячи загальнодоступним мовою async/await — це Promise.

Коли ви оголошуєте функцію як асинхронну, через чарівне слово async, ви говорите, що дана функція повертає Promise. Кожна річ яку ви очікуєте всередині цієї функції, використовуючи чарівне слово await, то ж повертає Promise.

Це дуже важливий момент для розуміння, як такі функції працюють і чого чекати при роботі з ними.

Давайте подивимося, як виглядає наш єдиноріг і розберемося як він працює.
Ось простий приклад асинхронного Redux екшену для виходу з кабінету:

export function logout(router) {
return async (dispatch) => {
try {
const {data: {success, message}} = await axios.get('/logout');

(success)
? dispatch({ type: LOGOUT_SUCCESS })
: dispatch({ type: LOGOUT_FAILURE, message });

} catch (e) {
dispatch({ type: LOGOUT_FAILURE, e.data.message });
}
};
}

А тепер йдемо від загального до приватного
Після прочитання низки статей і самостійно погравшись, я склав для себе невеликий бриф, відповідає на основні питання, з невеликими прикладами.

Що потрібно зробити щоб почати роботу?
Якщо не використовувати ніякої системи збирання, то достатньо встановити babel babel-runtime.

babel test.js -o test-compile.js —optional runtime —experimental

В інших випадках, краще дивитися налаштування виходячи системи складання і версії babel. Це дуже важливо, так як налаштування у версії babel5 і babel6 сильно розрізняються.

Як створюється асинхронна функція?
async function unicorn() {
let rainbow = await getRainbow();
return rainbow.data.colors
}

Створення асинхронної функції складається з двох основних частин:

1. Використання слова async перед оголошенням функції.

Як ми бачимо з прикладу c logout), це так само працює при використанні стрілочних функцій. Ще це працює для функцій класів і статичних функцій. В останньому випадку async пишеться після static.

2. В тілі самої функції ми повинні використовувати слово await.

Використання слова await сигналізує про те, що основний код чекав і не повертав відповідь, поки не виконатися якусь дію. Воно просто обробляє Promise для нас чекає поки він поверне resolve або reject. Таким чином, створюється враження, що код виконується синхронно.

* Для роботи з await функція повинна бути асинхронної і оголошена за допомогою ключового слова async. В іншому випадку це просто не буде працювати.

Як працює await і яку функцію виконує?
Як говорилося раніше, await очікує будь-Promise. Проводячи аналогію з роботою об'єкта Promise, можна сказати, що await виконує таку ж функцію, що і його метод .then(). Єдина суттєва різниця в тому, що вона не вимагає ніяких callback функцій для отримання і обробки результату. Власне за рахунок цього і створюється враження, що код виконується синхронно.

Добре, якщо await це аналог .then() у Promise, як же мені тоді зловити і обробити виключення?
async function unicorn() {
try {
let rainbow = await getRainbow();
return rainbow.data.colors;
} catch(e) {
return {
message: e.data.message,
somaText: 'Текст не легкого життя єдинорогів'
}
}
}

Так як код в синхронному стилі, з цієї причини ми можемо використовувати старий добрий try/catch для вирішення подібних завдань.

Додатково хочеться акцентувати на цьому увагу.



Використання try/catch це єдиний спосіб зловити і обробити помилку. Якщо з якихось причин ви вирішите його не використовувати або просто забули, це може призвести до відсутності можливості обробки, а так само втрати зовсім.

В який момент відбувається виконання коду наступного за await?
async function unicorn() {
let _colors = [];
let rainbow = await getRainbow();

if(rainbow.data.colors.length) {
_colors = rainbow.colors.map((color) => color.toUpperCase());
}

return _colors;
}

Код наступний після await, продовжує своє виконання тільки тоді, коли функція використовується з await поверне resolve або reject.

якщо функція використовується з await не повертає Promise?
Якщо функція використовується з await не повертає Promise, а ми вже знаємо, що await його чекає, то виконання коду продовжиться так як якщо б ми не використовували await взагалі.

Що якщо оголосити функцію асинхронної, але не використовувати await?
async function unicorn() {
let rainbow = getRainbow();
return rainbow;
}

В такому випадку, на виході ми отримаємо просто посилання на Promise функції getRainbow().

Що буде якщо я напишу кілька функцій використовують await поспіль?
async function unicorn() {
let rainbow = await getRainbow();
let food = await getFood();
return {rainbow, food}
}

Такий код буде виконуватися послідовно. Спочатку відпрацює getRainbow(), після того як вона поверне resolve або reject почне працювати getFood(). Один виклик, один результат.

А якщо мені потрібно одночасно отримати результат від декількох викликів?
async function unicorn() {
let [rainbow, food] = await Promise.all([getRainbow(), getFood()]);
return {rainbow, food}
}

Так як ми вже розібралися, що ми маємо справу з Promise. Отже можна використовувати метод .all() об'єкта Promise для вирішення такого роду завдань.

А для тих хто любить вишукані рішення:
async function unicorn() {
let [rainbow, food] = await* [getRainbow(), getFood()];
return {rainbow, food}
}

ще добре б знати для успішної роботи?
async function getAllUnicorns(names) {
return await* names.map(async function(name) {
var unicorn = await getUnicorn(name);
return unicorn;
});
}

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

Начебто все.

З основними теоретичними аспектами ми розібралися, якщо щось залишилося незрозумілим або у Вас є що доповнити, чекаю в коментарях.

А для мене настав час зібрати всю інформацію воєдино, і вирішити, що ж мені робити з нашим асинхронним єдинорогом. Брати з собою в релизное подорож або залишити будинки для своїх Pet проектів.

Висновки:
На перший погляд асинхронні функції викликають позитивні емоції. Така няшная штучка робить твій код синхронно подібним, що робить його більш лаконічним і читабельним.

Але це на перший погляд.

Так, код лаконічний і читабельний. Так, виглядає симпатично. Але як тільки ти намагаєшся писати щось складніше ніж звичайне API або складні взаимосвязные скрипти, наприклад, черга завдань для роботи з базою даних, одразу стикаєшся з проблемою асинхронного стека.

Майже всі вічно зелені браузери, з коробки, на 93%-98% підтримують фічі ES2015 (таблиця). Для мене це означає, що починаючи новий проект, виходячи з вимог і стека, я вже задумаюся про необхідність babel на проекті.

Але, якщо я вирішу використовувати async/await, я буду зобов'язаний використовувати babel. І не можу сказати що це додасть краси мій код. Адже офіційно async/await немає, і не відомо чи буде взагалі. І це для мене великий мінус.

Так само мені дуже не подобається той факт, що якщо я забув застосувати await або просто не вдалий копіпаст, замість автоматичного вильоту на помилку, я нічого не отримаю, крім посилання на Promise. Це може бути загрожує наслідками, особливо коли великий проект з кількома розробниками.

І останнє.

Більшість завдань з використанням async/await прекрасно вирішуються за допомогою генераторів.

По-перше, у них і підтримка краще.
По-друге, робота генераторів буде більш природна і передбачувана.
По-третє сам babel наводить такий код до генераторів при особливих налаштуваннях пример1, пример2.

Разом
Відповідаю собі на питання поставлене на самому початку:
Якщо я і буду використовувати асинхронні функції, то тільки на своїх Pet проектах і тільки для написання API.

Наприклад мені сподобалося використовувати їх в екшенах для Redux. Виглядає все красиво і гармонійно.

Цей матеріал я писав в першу чергу для себе, щоб розібратися з питанням мене питанням. Якщо даний матеріал буде ще комусь корисний, я буду дуже радий.

Також, в наступній статті я б хотів детально порівняти різні підходи до реалізації асинхронності (колбэки, промисы, генератори, атоми). Щоб це було зрозуміло не лише гуру, але і людям, які тільки починають свій шлях в javascript.

дякую Всім за увагу. Удачі!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.