Search code examples
javascriptpromiseevent-loop

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)


Solution

  • 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');