How do async and await work in JavaScript?
An async function always returns a promise. When you call one, its body runs synchronously up to the first await, and then the function returns and the rest of the body is scheduled to continue later. The await keyword pauses that continuation and waits for the awaited value to settle.
Under the hood, await is roughly the same as attaching then and catch handlers to the awaited value. The compiler transforms an async function into one that returns a promise and wires up the continuations for you, which is why an async function can never throw synchronously to its caller. Any error becomes a rejection instead.
The important consequence is that the code before the first await is not asynchronous in any meaningful sense. If you do expensive work there, it blocks just like ordinary synchronous code. If you want a function to yield to the runtime before it starts, put an await at the top, or use the setTimeout trick of resolving a promise with no value.
Compared with a promise chain, the benefit is that the code keeps its shape. Sequential steps read top to bottom, the error handling is a single try and catch block rather than a second callback, and loops with await work naturally. The cost is that every function that awaits has to be async, so it returns a promise, and you have to be careful about the promise-returning function that forgets to await and therefore returns the wrong value.