Apparently, using window.postMessage is a preferred way to queue an async javascript callback over window.setTimeout(fn, 0)
across all modern browsers. I could not find a similar comparison between window.postMessage and MessagePort.postMessage (using the same MessageChannel for sending and receiving messages asynchronously). Has anyone seen or done any timing? Does MessagePort.postMessage work for this purpose at all (where available)?
[EDITED] MessagePort.postMessage
does work for this, but window.postMessage
remains a preffered way, IMO (see my answer).
postMessage() The window. postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
postMessage method allows different windows or iframes to communicate directly, even if they were loaded from different origins, circumventing the usual same-origin policy. The sender of the message can restrict the origin of the receiver by specifying a target origin.
Security-Reviewing Uses of postMessage()postMessage is generally considered very secure as long as the programmer is careful to check the origin and source of an arriving message. Acting on a message without verifying its source opens a vector for cross-site scripting attacks.
The postMessage() function is asynchronous, meaning it will return immediately. So you can not do synchronous communication with it. In your example, the posted message will vanish in the void, because there is no listener for the message event at the time the postMessage() function is executed.
[UPDATE] Added a test for setImmediate
and a JSFiddle. Related, there's a cross-browser implementation of setImmediate
and ASAP library used by Q for a promise resolution/rejection.
I went ahead and did some timing, using a modified version of David Baron's code, the results are below:
setTimeoutMC
- using MessageChannel
setTimeoutPM
- using window.postMessage
setTimeout(0)
- using setTimer
IE10:
2000 iterations of setTimeoutMC took 126 milliseconds.
2000 iterations of setTimeoutPM took 190 milliseconds.
2000 iterations of setTimeout(0) took 7986 milliseconds.
Chrome v29.0.1547.66:
2000 iterations of setTimeoutMC took 144 milliseconds.
2000 iterations of setTimeoutPM took 81 milliseconds.
2000 iterations of setTimeout(0) took 10589 milliseconds.
Clearly, window.postMessage is the winner here (considering the level of existing cross-browser support for it). The looser is window.setTimeout(fn, 0)
and should be avoided wherever possible.
Code:
<!DOCTYPE html>
<html>
<head>
<!-- http://stackoverflow.com/q/18826570/1768303 -->
<!-- based on http://dbaron.org/log/20100309-faster-timeouts -->
<!-- requires IE10 or Chrome. Firefox doesn't support MessageChannel yet -->
<title></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<script type="text/javascript">
// setTimeoutMC via MessageChannel
(function () {
"use strict";
var i = 0;
var timeouts = {};
var setApiName = "setTimeoutMC";
var clearApiName = "clearTimeoutMC";
var channel = new MessageChannel();
function post(fn) {
if (i === 0x100000000) // max queue size
i = 0;
if (++i in timeouts)
throw new Error(setApiName + " queue overflow.");
timeouts[i] = fn;
channel.port2.postMessage(i);
return i;
}
channel.port1.onmessage = function (ev) {
var id = ev.data;
var fn = timeouts[id];
if (fn) {
delete timeouts[id];
fn();
}
}
function clear(id) {
delete timeouts[id];
}
channel.port1.start();
channel.port2.start();
window[setApiName] = post;
window[clearApiName] = clear;
})();
// setTimeoutPM via window.postMessage
(function () {
"use strict";
var i = 0;
var timeouts = {};
var setApiName = "setTimeoutPM";
var clearApiName = "clearTimeoutPM";
var messageName = setApiName + new Date().getTime();
function post(fn) {
if (i === 0x100000000) // max queue size
i = 0;
if (++i in timeouts)
throw new Error(setApiName + " queue overflow.");
timeouts[i] = fn;
window.postMessage({ type: messageName, id: i }, "*");
return i;
}
function receive(ev) {
if (ev.source !== window)
return;
var data = ev.data;
if (data && data instanceof Object && data.type === messageName) {
ev.stopPropagation();
var id = ev.data.id;
var fn = timeouts[id];
if (fn) {
delete timeouts[id];
fn();
}
}
}
function clear(id) {
delete timeouts[id];
}
window.addEventListener("message", receive, true);
window[setApiName] = post;
window[clearApiName] = clear;
})();
// timing
function runtest() {
var output = document.getElementById("output");
var outputText = document.createTextNode("");
output.appendChild(outputText);
function printOutput(line) {
outputText.data += line + "\n";
}
var n = 2000;
var i = 0;
var startTime = Date.now();
setTimeoutMC(testMC);
function testMC() {
if (++i === n) {
var endTime = Date.now();
printOutput(n + " iterations of setTimeoutMC took " + (endTime - startTime) + " milliseconds.");
i = 0;
startTime = Date.now();
setTimeoutPM(testPM, 0);
} else {
setTimeoutMC(testMC);
}
}
function testPM() {
if (++i === n) {
var endTime = Date.now();
printOutput(n + " iterations of setTimeoutPM took " + (endTime - startTime) + " milliseconds.");
i = 0;
startTime = Date.now();
setTimeout(test, 0);
} else {
setTimeoutPM(testPM);
}
}
function test() {
if (++i === n) {
var endTime = Date.now();
printOutput(n + " iterations of setTimeout(0) took " + (endTime - startTime) + " milliseconds.");
}
else {
setTimeout(test, 0);
}
}
}
</script>
</head>
<body onload="runtest()">
<pre id="output"></pre>
</body>
</html>
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