An async function runs synchronously to its first await, then tears
The common read: calling an async function defers its whole body; nothing inside it runs until the event loop gets around to it later.
It's a reasonable thing to believe: you write the word async on a function, and it's tempting to read that as "this whole thing happens off to the side, later." It doesn't. Calling a() pushes a real frame onto your call stack, the same stack a plain function call would use, and everything in a() runs on it exactly like ordinary code, right up until the function hits an await.
That's the tear. At await, the runtime pops the function's frame off the stack, and the code that comes after the await stops being "running now." It becomes a continuation: a chip that goes into the microtask queue you already have a name for. The function hasn't finished. It's paused mid-body, and the rest of it is now somebody else's turn to run, once the stack is clear and the queue gets checked.
That's why a1 logs before main: a() is a real call, running on your stack like any other. And it's why a2 logs after main: the tear moved it into the queue, and the queue only runs once the stack is completely empty. Read this as one rule: sync until the first await, then tear.
