Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to queue a microtask if the browser doesn't support native Promises?

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)

like image 899
Snow Avatar asked Dec 18 '19 11:12

Snow


2 Answers

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.

like image 76
Snow Avatar answered Oct 30 '22 07:10

Snow


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');
like image 32
x00 Avatar answered Oct 30 '22 08:10

x00