The microtask queue empties before the loop takes a task
The common read: Whichever call gets queued first also runs first, so the timer callback (queued at line 2, before the promise callback at line 3) has to log before it.
Two queues hold work while the running code finishes. From run-to-completion: ready work waits in the task queue, and the loop takes one task per turn. A promise callback does not go to the task queue. It waits in a second queue, the microtask queue, and the loop handles that queue by a different rule.
Every time the call stack empties, meaning the currently running code has finished, and before the loop is allowed to take its next task, it runs a microtask checkpoint: it drains the microtask queue completely, running every microtask in the order they were queued, not only the first one. Only when the microtask queue is empty does the loop take one item from the task queue for the turn.
That is why C logged before B above. Both were queued while the script ran. The script finishes, the stack empties, the checkpoint runs, and C is the only item in the microtask queue, so C runs immediately. B is still in the task queue at that point, and B waits for its own turn after the checkpoint has emptied the microtask queue.

