setImmediate, MessageChannel, postMessage broken on Internet Explorer 10

The JavaScript setImmedate function has been proposed and promoted as a faster alternative to setTimeout(fn, 0) (and cleaner than postMessage). While the HTML spec clamps setTimeout(fn, 0) to a minimum delay of 4 ms, setImmediate(fn) is defined to run the function immediately after any pending events have been handled.

The new API has seen opposition in both WebKit and Gecko developers. Currently, the only browser to implement the function is Internet Explorer 10, in addition to node.js. Unfortunately, IE10 has a serious flaw that renders the API practically useless on IE10 Mobile and dubious on IE10 Desktop.

IE10 Mobile

On Windows Phone 8, instead of yielding to any currently pending events, setImmediate yields also to any events queued later on. Essentially, it means “run this function when you have nothing else to do”. If the browser is constantly busy running other code and rendering, the function queued by setImmediate will never be run.

As a consequence, simply adding a spin.js spinner on our login page caused Q promises to stop working completely.

The bug can be demonstrated by a simple demo page. This page has two competing function loops: runSetTimeout which uses setTimeout(fn, 0) and does a busy-loop for 500 ms, and runSetImmediate that uses setImmediate. When run on IE10 desktop or on node.js, both functions are run alternately. On WP8, runSetImmediate is never run.

Two variations of the demo page confirm the cause: If the busy-loop is removed from runSetTimeout, then runSetImmediate is initially run a couple of dozen times, until updating the DOM becomes slow enough to cause it to yield forever. If the logging action is changed to replace the DOM (which is a constant-time action), then both methods are run properly on WP8 as well.

IE10 Desktop

Initially we assumed this to be a bug only in IE10 Mobile.  However, when creating the test pages, I discovered conditions where runSetImmediate fails to run on IE10 Desktop as well.

The problem can be reproduced by opening the second test page, starting the test and letting it run for a short while (presumably until the DOM manipulation is sufficiently slow).  Calling of runSetImmediate stops when either a) moving the mouse cursor, or b) placing the mouse cursor on top of the changing log lines.

Evidently, the combination of a setTimeout(fn, 0) loop, DOM manipulation and DOM events being fired is enough to break setImmediate even on IE10 Desktop.

It seems unlikely that this would cause any actual problems in IE10 Desktop, though it might have a performance effect on setImmediate.  In some rare cases, though, it might cause mysterious lock-ups with callbacks not being properly called.

MessageChannel & postMessage

After identifying that setImmediate was the cause of our woes, we tried forcing Q promises to use an alternative method for waiting until the next tick.  After setImmediate, the next fallback is to use MessageChannel.  Unfortunately, this failed to work as well.

This is demonstrated by a page using Q promises.  Unless you disable both setImmediate and MessageChannel, the same bugs appear.

We assume that MessageChannel uses the same internal mechanism as setImmediate, and thus suffers from the same issues.

Update:  postMessage also suffers from the same bug.  Seems like there’s no fully functional zero-time timeout on IE10.

Update 2:  onreadystatechange looks like a functioning way of getting zero-delay asynchronous callbacks on IE10.  On IE10 Mobile onreadystatechange and setTimeout are called alternately, on IE10 Desktop onreadystatechange pre-empts setTimeout so the latter is never called.  Therefore it seems that on IE10 Desktop onreadystatechange is called at the end of the current tick.

The workaround

Fortunately, it’s simple to force libraries not to use the broken APIs:

if (navigator && navigator.appName == 'Microsoft Internet Explorer') {
    window.setImmediate = undefined;
    window.MessageChannel = undefined;

I’m unaware of any better way of detecting a broken setImmediate implementation than targeting the browser.  Of course, you’re less fortunate if you would actually like to use MessageChannel on WP8.

We have reported the bug to Microsoft and various promise library maintainers.

I’d like to thank our development team — Tomas Östman, Jussi Seppälä and Kalle Korpiaho — for their efforts in deciphering this bug.

This entry was posted in Bugs, IE, JavaScript and tagged , , , . Bookmark the permalink.

2 Responses to setImmediate, MessageChannel, postMessage broken on Internet Explorer 10

  1. It looks like this bug is still present in IE11.

  2. Pingback: HTML5 Weekly No.107 | ENUE Blog

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s