While looking at this code:
<script>
console.log(1);
new Promise(r=>r()).then(() => console.log(2));
</script><script>
console.log(3);
</script>
it seems like 2 should be printed after 3, because it should have been queued as a microtask, just like it behaves here:
<script>
console.log(1);
new Promise(r=>r()).then(() => console.log(2));
console.log(3);
</script>
I can't find a good explanation - why would the JS engine bother to empty the microtask queue before continuing to process the next script element?
It's not the JS engine but the DOM one, which after running a classic script has to clean up after running a script (step 8). This clean up algorithm does a microtask checkpoint.
- If the JavaScript execution context stack is now empty, perform a microtask checkpoint.
So the microtask isn't really checked "before" the </script>
end tag, but rather right after the script got executed (which technically happens when the end tag is met).
You can even make some funky stuff like forcing a MutationObserver to notify of changes in the DOM right in the middle of parsing an element's content This can come handy for testing.:
<script>
function myObserver(mutationsList) {
for (let mutation of mutationsList) {
for (let n of mutation.addedNodes) {
if (n.id === 'parent') {
console.log("innerHTML: ", n.innerHTML);
}
}
}
}
var observer = new MutationObserver(myObserver);
observer.observe(document.body, {
childList: true,
subtree: true
});
</script>
<div id="parent">
<span>This will be parsed</span>
<script>/* This script forces the MutationObserver to kick its notifications */</script>
<span>Not yet parsed</span>
</div>