setTimeout(0) vs window.postMessage vs MessagePort

2019-02-07 09:01发布

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).

1条回答
该账号已被封号
2楼-- · 2019-02-07 09:25

[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>
查看更多
登录 后发表回答