await inside an event handler
Yes, you can use await inside an event handler, but the handler itself has to be declared async. It is an ordinary async function that the browser calls: it returns a promise nobody waits for, so the UI is never blocked, and handling errors is entirely your responsibility.
Theory
TL;DR
awaitis allowed only inside a function markedasync, otherwise the engine throws aSyntaxError.- The working form is
element.addEventListener('click', async () => { ... }). - The browser does not wait for the promise the handler returns and has no idea there is an
awaitinside. - Errors from an
asynchandler do not join the normal exception flow, so you needtry/catch. - Call
preventDefault()andstopPropagation()synchronously, before the firstawait. - It is convenient to move the logic into a separate
asyncfunction and pass it as the handler.
Quick example
// 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);
});Now everything is legal: inside the handler execution waits for fetch, but the event itself does not freeze the page, and the rest of the code keeps running.
The basic rule: await lives only inside async
await is not a global construct, it is part of the syntax of async functions. If a handler written as a plain function or arrow contains await, the script will not even start: the error happens at parse time, not on click.
There is exactly one exception, top level await in ES modules, and it does not apply to event handlers, because a handler body is always a function body.
A practical example: loading data on click
<button id="load">Load data</button>
<div id="result"></div>const btn = document.querySelector('#load');
const result = document.querySelector('#result');
btn.addEventListener('click', async () => {
result.textContent = 'Loading...';
try {
const res = await fetch('https://api.example.com/posts/1');
const post = await res.json();
result.textContent = post.title;
} catch (err) {
result.textContent = 'Failed to load';
}
});What happens here:
- the click starts the
asynchandler; await fetch(...)pauses execution inside that function;- once the data arrives it goes into the DOM;
- the interface stays responsive, because JavaScript remains asynchronous and the event loop is free.
Moving the logic into a separate async function
Sometimes it is cleaner to keep the logic away from the subscription:
async function handleClick() {
const data = await fetchData();
render(data);
}
button.addEventListener('click', handleClick);This behaves identically: addEventListener simply receives a function reference, and the fact that it is async is an implementation detail. This style is also easier to test and reuse.
What the browser does with the handler's promise
This is the part interviewers are really asking about:
-
Nothing awaits the handler.
addEventListenerdoes not know there is anawaitinside. Code after the handler call runs immediately and the returned promise is simply discarded. -
You must catch errors yourself. An unhandled rejection ends up in
unhandledrejectionand can easily go unnoticed:javascriptbtn.addEventListener('click', async () => { try { await riskyOperation(); } catch (e) { console.error('Error:', e); } }); -
preventDefault()andstopPropagation()take effect instantly, but only while the handler is still running synchronously. After the firstawaitthe browser has finished dispatching the event and the default action can no longer be cancelled, so call them at the very top. -
Double clicks. While an
awaitis pending the user can click again and start a second request. The fix is simple: disable the button or keep anisLoadingflag.
Summary table
| Question | Answer |
|---|---|
Can you use await in a handler? | Yes, if the handler is async |
Can you write await without async? | No, it is a SyntaxError |
| Does it block the interface? | No, the event loop stays free |
| Do you have to catch errors? | Yes, with try/catch |
Can the async function live separately? | Yes, and it is the cleaner style |
| Does the browser wait for the handler to finish? | No, the returned promise is ignored |
A simple rule: await inside a handler is fine, as long as the handler is async and the errors are handled.
Common mistakes
- Forgetting
asyncon the handler. The script fails with aSyntaxErrorbefore the first click, and the page ends up with no handlers at all. - Calling
preventDefault()after anawait. The form has already been submitted and the link has already navigated: there is nothing left to cancel. - Assuming the browser waits for the handler. It does not, which is why asynchronous work inside
beforeunloadnever gets the chance to finish. - Not handling errors. A rejected promise from a handler does not stop the program and often disappears silently, leaving the interface stuck on "Loading...".
- Not guarding against repeated clicks. Several quick clicks fire several parallel requests, and the DOM ends up showing whichever finished last rather than whichever was clicked last.
- Marking a handler
asyncfor no reason. With no asynchronous work inside,asynconly adds a microtask and makes the code harder to read.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.