What is the difference between a microtask and a macrotask?
A macrotask is a chunk of work the event loop takes from the task queue, one at a time. A microtask is a piece of work that goes into a separate queue, and that queue is drained completely before the loop takes the next macrotask.
The difference is about priority and about how much of the queue gets processed at once. The microtask queue is emptied fully, and it includes any microtasks those microtasks queue. The task queue is taken one entry at a time, and the microtask queue is drained after each one.
In practice the split is this. Promise reactions, the queueMicrotask function and the MutationObserver callbacks are microtasks. Timers, network completions, user events and the initial script execution are macrotasks.
The consequence is the ordering people find surprising. A promise callback always runs before a timer callback, even when the timer was scheduled first and has a delay of zero. And a promise callback queued from inside a timer callback runs before the next timer, because the microtask queue is drained after every task.
One more distinction that matters for design. Because the microtask queue is drained completely, a chain of promise callbacks can starve the event loop. If each one queues another, timers and input handling never run and the page appears frozen. Yielding to a macrotask, by scheduling a timer with a zero delay, gives the runtime a chance to render and to handle input again.