In HTML terms, the event loop for a page or set of pages from the same domain can have multiple task queues. Tasks from the same task source always go into the same queue, with the browser choosing which task queue to use next.
Tasks to run timer call backs come from the timer task source and go in the same queue. Let's call this queue task queue "A".
The ECMAscript 2015 (ES6) specification requires tasks to run Promise reaction callbacks to form their own job queue called "PromiseJobs". ECMAscript and HTML specifications do not use the same language, so let's notionally equate ECMA's "Promise Job queue" with HTML task queue "B" in the browser - at least a different queue to the one used by timers.
Theoretically a browser could choose tasks from either queue A or B to run, but in practice the promise task queue gets higher priority and will empty before a timer call back gets run.
This is why "h" gets logged last. Promise then
calls on fulfilled promises place jobs in the promise queue, which get executed with higher priority than timer call backs. The promise queue only becomes empty after console.log(3)
has been executed, which allows the timer call back to execute.
Advanced
ECMAScript guardians chose not to use HTML5 terminology or description of task queues in their specification because ECMAScript can run in more environments than just HTML browsers.
Native implementation of promise queues may use a "micro task" queue instead of a separate dedicated promise task queue. Micro queued jobs are simply run after the current script thread and any tasks previously added to the micro queue complete.
Detail of micro task queuing is not required to understand promises.
Promise polyfills for browsers which lack native support for promises (all versions of IE etc) may use timers and not behave in exactly the same way as native implementations when it comes to the order of promise reactions and timer call backs.