Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «await в обробнику події». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Так, можна, але обробник має бути оголошений як `async`: `await` дозволений лише всередині `async`-функції, інакше буде `SyntaxError`.** Браузер викликає обробник і не чекає на повернений ним promise, тому інтерфейс не блокується, але й ніхто не побачить помилки, якщо не загорнути асинхронну частину в `try/catch`. Ще одна деталь: `preventDefault()` і `stopPropagation()` треба викликати синхронно, до першого `await`. ```javascript button.addEventListener('click', async (event) => { event.preventDefault(); // synchronously, before the first await try { const res = await fetch('/api/data'); console.log(await res.json()); } catch (err) { console.error('Request failed:', err); } }); ``` **Ключове:** `async` на обробнику плюс `try/catch` усередині, а `preventDefault()` завжди до першого `await`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Так, `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` лише додає мікрозадачу і ускладнює читання коду.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.