Skip to main content

await в обробнику події

Так, await в обробнику події використовувати можна, але сам обробник треба оголосити як async. Це звичайна async-функція, яку викликає браузер: вона повертає promise, на який ніхто не чекає, тому інтерфейс не блокується, а відповідальність за помилки повністю лежить на вас.

Теорія

TL;DR

  • await дозволений тільки всередині функції з async, інакше двигун кине SyntaxError.
  • Робочий варіант: element.addEventListener('click', async () => { ... }).
  • Браузер не чекає на promise, який повернув обробник, і не знає, що всередині є await.
  • Помилки з async-обробника не потрапляють у звичайний потік винятків, тож потрібен try/catch.
  • preventDefault() і stopPropagation() викликайте синхронно, до першого await.
  • Логіку зручно виносити в окрему async-функцію і передавати її як обробник.

Швидкий приклад

javascript
// Wrong: await inside a plain, non-async function. button.addEventListener('click', () => { await fetch('/api/data'); // SyntaxError }); // Correct: the handler itself is async. button.addEventListener('click', async () => { const response = await fetch('/api/data'); const data = await response.json(); console.log(data); });

Тепер усе законно: всередині обробника виконання чекає на fetch, але сама подія не «заморожує» сторінку, решта коду продовжує працювати.

Базове правило: await живе лише в async

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

Виняток лише один: top-level await у модулях ES, але до обробників подій це не стосується, бо тіло обробника завжди є тілом функції.

Практичний приклад: завантаження даних по кліку

html
<button id="load">Завантажити дані</button> <div id="result"></div>
javascript
const btn = document.querySelector('#load'); const result = document.querySelector('#result'); btn.addEventListener('click', async () => { result.textContent = 'Завантаження...'; try { const res = await fetch('https://api.example.com/posts/1'); const post = await res.json(); result.textContent = post.title; } catch (err) { result.textContent = 'Помилка завантаження'; } });

Що тут відбувається:

  • по кліку запускається async-обробник;
  • await fetch(...) призупиняє виконання всередині цієї функції;
  • після завантаження дані потрапляють у DOM;
  • інтерфейс при цьому не блокується, бо JavaScript залишається асинхронним, а цикл подій вільний.

Винесення логіки в окрему async-функцію

Іноді зручніше тримати логіку окремо від підписки на подію:

javascript
async function handleClick() { const data = await fetchData(); render(data); } button.addEventListener('click', handleClick);

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

Що браузер робить з promise обробника

Це найважливіша частина відповіді на співбесіді:

  • Обробник не чекають. addEventListener не знає, що всередині є await. Код після виклику обробника виконується одразу, а повернений promise просто ігнорується.

  • Помилки треба ловити самостійно. Необроблене відхилення піде в unhandledrejection і може лишитися непоміченим:

    javascript
    btn.addEventListener('click', async () => { try { await riskyOperation(); } catch (e) { console.error('Error:', e); } });
  • preventDefault() і stopPropagation() працюють миттєво, але тільки поки обробник виконується синхронно. Після першого await браузер уже завершив обробку події і скасувати дію за замовчуванням неможливо, тому викликайте їх на самому початку.

  • Подвійні кліки. Поки триває await, користувач може клікнути ще раз і запустити другий запит. Захист простий: вимкнути кнопку або тримати прапорець isLoading.

Підсумкова таблиця

ПитанняВідповідь
Чи можна використовувати await в обробнику?Так, якщо обробник async
Чи можна написати await без async?Ні, буде SyntaxError
Чи блокує це інтерфейс?Ні, цикл подій лишається вільним
Чи треба ловити помилки?Так, через try/catch
Чи можна винести async-функцію окремо?Так, це навіть чистіший стиль
Чи чекає браузер на завершення обробника?Ні, повернений promise ігнорується

Просте правило: await в обробнику це нормально, головне щоб обробник був async, а помилки були оброблені.

Типові помилки

  • Забути async на обробнику. Скрипт падає з SyntaxError ще до першого кліку, і сторінка залишається без жодних обробників.
  • Викликати preventDefault() після await. Форма вже відправилась, посилання вже спрацювало: скасовувати нічого.
  • Покладатися на те, що браузер дочекається обробника. Він не дочекається, тому в beforeunload асинхронна робота просто не встигне виконатись.
  • Не обробляти помилки. Відхилений promise з обробника не зупиняє програму й часто зникає без сліду, лишаючи інтерфейс у стані «Завантаження...».
  • Не блокувати повторні кліки. Кілька швидких кліків дають кілька паралельних запитів, і в DOM потрапляє результат того, який завершився останнім, а не останнього натиснутого.
  • Робити обробник async без потреби. Якщо асинхронності немає, зайвий async лише додає мікрозадачу і ускладнює читання коду.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.