【问题标题】:setTimeout(0) vs window.postMessage vs MessagePort.postMessagesetTimeout(0) vs window.postMessage vs MessagePort.postMessage
【发布时间】:2014-02-03 10:23:05
【问题描述】:

显然,使用window.postMessagepreferred way 在所有现代浏览器中通过window.setTimeout(fn, 0) 对异步javascript 回调进行排队。我在window.postMessageMessagePort.postMessage 之间找不到类似的比较(使用相同的MessageChannel 异步发送和接收消息)。有没有人看过或做过任何计时? MessagePort.postMessage 是否完全用于此目的(如果可用)?

[EDITED] MessagePort.postMessage 确实适用于此,但 window.postMessage 仍然是首选方式,IMO(请参阅我的回答)。

【问题讨论】:

标签: javascript html asynchronous timeout message


【解决方案1】:

[UPDATE] 添加了对setImmediateJSFiddle 的测试。相关,setImmediateASAP library 中有一个 cross-browser implementationQ 用于承诺解决/拒绝。

我继续做了一些计时,使用David Baron's code的修改版本,结果如​​下:

setTimeoutMC - 使用MessageChannel
setTimeoutPM - 使用window.postMessage
setTimeout(0) - 使用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.

显然,window.postMessage 是这里的赢家(考虑到现有的跨浏览器支持水平)。较宽松的是window.setTimeout(fn, 0),应尽可能避免使用。

代码:

<!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>

【讨论】:

  • 在支持 Promise 的浏览器上(根据MDN Promise docs,这是大多数主流浏览器),Promise.resolve 可能会更快。请参阅我的modified Fiddle,在我的测试中,它在最新的 Firefox nightly build 中速度提高了 5-6 倍。
  • @MykMelez 哇。适合原生 Promise!为什么 setTimeout 不能这么快?有原因吗? setTimeout 也可以替换为 Promise.resolve 实现,但这会导致问题吗?
  • @trusktr developer.mozilla.org/en-US/docs/Web/API/WindowTimers/… 有详细信息,但总结是连续的setTimeout(fn, 0) 调用被“钳制”到最小超时 4ms,所以它永远不会少于那个时间解决它们。
  • 我的问题是测量执行速度以进行这些调用并等待直到完成或只是进行调用。由于存在巨大差异,我只是想知道这是否是苹果与苹果之间的比较。
  • @noseratio 不错的链接! “哦,IE 几乎消失了 :)” Yaaaaaay。 ??
猜你喜欢
  • 2021-10-24
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 2014-06-04
  • 2016-08-01
  • 1970-01-01
  • 2012-06-03
  • 1970-01-01
相关资源
最近更新 更多