It's better to write code that doesn't rely on the timing of immediate callbacks (like microtasks vs macrotasks), but let's put that aside for the moment.
setTimeout
queues a macrotask, which, at a minimum, waits to start until all microtasks (and microtasks that they spawn) finish. Here's an example:
console.log('Macrotask queued');
setTimeout(function() {
console.log('Macrotask running');
});
Promise.resolve()
.then(function() {
console.log('Microtask running');
});
console.log('Microtask queued');
console.log('Last line of script');
The behavior of a .then
on a resolved Promise is fundamentally different from the behavior of an immediate setTimeout
callback - the Promise .then
will run first, even if the setTimeout
was queued first. But only modern browsers support Promises. How can the special functionality of a microtask be properly polyfilled if Promise
doesn't exist?
If you try to imitate a .then
's microtask by using setTimeout
, you'll be queuing a macrotask, not a microtask, so the badly-polyfilled .then
won't run at the right time if a macrotask is already queued.
There's a solution using MutationObserver
, but it looks ugly, and isn't what MutationObserver
is for. Also, MutationObserver
is not supported on IE10 and earlier. If one wants to queue a microtask in an environment that doesn't natively support Promises, are there any better alternatives?
(I'm not actually trying to support IE10 - this is just a theoretical exercise on how microtasks can be queued without Promises)
I saw that mutationObserver
callbacks use microtasks, and luckily, IE11 supports it, so I had the idea to queue a microtask in IE11 by saving the callback and then immediately triggering the observer by changing an element:
var weirdQueueMicrotask = (function() {
var elementThatChanges = document.createElement('div');
var callback;
var bool = false;
new MutationObserver(function() {
callback();
}).observe(elementThatChanges, { childList: true });
return function(callbackParam) {
callback = callbackParam;
elementThatChanges.textContent = bool = !bool;
};
})();
setTimeout(function() {
console.log('Macrotask running');
});
console.log('Macrotask queued');
weirdQueueMicrotask(function() {
console.log('Microtask running');
});
console.log('Microtask queued');
console.log('Last line of script');
You can open up IE11 and see the above working, but the code looks strange.
If we are talking about IE you can use setImmediate
https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
Also, MutationObserver is not supported on IE10 and earlier.
setImmediate
is supported on IE10. So plus one IE version.
And, if your are interested, plus Node.js.
There's a solution using MutationObserver, but it looks ugly, and isn't what MutationObserver is for.
There are other possible polyfills, here is a couple of implementations: https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js (this one is mentioned in MDN) https://github.com/taylorhakes/setAsap/blob/master/setAsap.js (a more simple one)
And as almost all polyfills they are ugly as well.
But anyway, here is an example in its essence (using postMessage), and I think it is least ugly of all (but also not a true polyfill)
var setImmediate = (function() {
var queue = [];
function on_message(e) {
if(e.data === "setImmediateMsg") queue.pop()()
}
if(window.addEventListener) { // IE9+
window.addEventListener('message', on_message)
} else { // IE8
window.attachEvent('onmessage', on_message)
}
return function(fn) {
queue.unshift(fn)
window.postMessage("setImmediateMsg", "*")
}
}())
setTimeout(function() {
console.log('Macrotask running');
});
console.log('Macrotask queued');
setImmediate(function() {
console.log('Microtask running');
});
console.log('Microtask queued');
console.log('Last line of script');
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With