如何与 SharedWorker 共享数据

How to share data with SharedWorker

网上和Whosebug上已经有很多关于SharedWorker的讨论和教程,但是none确实实现了最基本的目标——在两个shared worker之间直接传递数据。

对我来说,SharedWorker 优于专用网络 Worker 的优势在于前者允许通过不同的选项卡、iframe 和线程进行直接通信。我尝试了以下方法:

(shared.html:)

<!DOCTYPE html><html><head></head><body>
<button onclick="init('a')">Initiate A</button>
<button onclick="init('b')">Initiate B</button>
<script>

var a,b;
function init(v) {
    if (v=='a'){
        a = (new SharedWorker('./shared.js')).port;
        a.start();
        a.postMessage({type:'start', port:b})
    } else {
        b = (new SharedWorker('./shared.js')).port;
        b.start();
        b.postMessage({type:'start', port:a});    // <== error here
    }
}

</script></body></html>

(shared.js)

let peer = null;

onconnect = function (ev) {
    let port = ev.ports[0];
    port.onmessage = (e) => {
        if (e.data.type=='start' && e.data.port){
            peer=e.data.port;
        } else if (e.data.type=='msg' && peer){
            setInterval(()=>{
                peer.postMessage({type:'msg',msg:'greetings!'});
            },2000);
        }
    }
    port.start();
}

点击 'Initiate A' 和 'Initiate B' 后,我在控制台上收到以下错误消息:

shared.html:15 未捕获的 DOMException:无法在 'MessagePort' 上执行 'postMessage':无法克隆 MessagePort,因为它未传输。

换句话说,我无法通过端口。

SharedWorker 也有任何用处。看来大多数情况下普通的dedicated Worker就够用了

所以基本上你在使用 SharedWorkers 是错误的。

来自文档:https://developer.mozilla.org/de/docs/Web/API/SharedWorker

The SharedWorker interface represents a specific kind of worker that can be accessed from several browsing contexts, such as several windows, iframes or even workers.

这意味着您可以跨多个 windows/选项卡/浏览上下文

交流和计算内容
|-----------|    |-----------|
|  Window 1 |    |  Window 2 |
|           |    |           |
|           |    |           |
|-----------|    |-----------|
      |                |
      __________________
              |
        |-----------|
        |   Worker  |
        |           |
        |-----------|

发送在上下文中启动 worker 将在 SharedWorker 上打开一个端口

//============================================
//== Site Script
//============================================

var worker = new SharedWorker('something.js');
worker.port.start(); // this will trigger the on connect event on the webworker
// this will also start the worker IF this is the first call!

// recieve message from worker
worker.port.addEventListener('message', message => {
  console.log(message);
});

// send a mesasge to the worker
worker.port.postMessage(['I have a nice message for all']);



//============================================
//== Shared Worker
//============================================
const allPorts = [];

onconnect = function(e) {
  // the incoming port
  var port = e.ports[0];
  allPorts.push(port);

  port.addEventListener('message', function(e) {
    // get the message sent to the worker
    var message = e.data[0];
    // send the message to ALL connected worker ports!
    allPorts.forEach(port => {
      port.postMessage(message);
    })
  });

  port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
}

您可以向 worker 发送附加端口,但 MessagePort 是可传输对象。这些需要在发送时添加到传输列表中。

const startA = document.getElementById('startA');
const startB = document.getElementById('startB');

const workerScript = 'console.log("started")';
const blob = new Blob([workerScript]);
const workerScriptURL = URL.createObjectURL(blob);

const worker = new Worker(workerScriptURL);
const messageChannel = new MessageChannel();

worker.postMessage({port: messageChannel.port2}, [messageChannel.port2]);
//                                                    ^ This is the transfer list!

如果您只想将数据传递到其他上下文,请使用 BrodcastChannel:

https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API

** 编辑 **

这是一个工作演示。尝试在一个选项卡上打开 shared.html 并在另一个选项卡上打开 shared2.html。您将在第二个选项卡上看到,数字不会从 0 开始。

(shared.html)

<!DOCTYPE html><html><head></head><body>
<button onclick="init()">Initiate</button>
<script>

function init() {
    w = (new SharedWorker('./shared.js')).port;
    w.start();
    w.postMessage(0);
    w.onmessage=e=>{
        console.log(e.data);
        w.postMessage(e.data[0]+1);
    };
}

</script></body></html>

(shared2.html)

<!DOCTYPE html><html><head></head><body>
<button onclick="init()">Initiate</button>
<script>

function init() {
    w = (new SharedWorker('./shared.js')).port;
    w.start();
    w.onmessage=e=>{
        console.log(e.data);
    };
}

</script></body></html>

(shared.js)

const ports = [];

onconnect = function (ev) {
    let port = ev.ports[0];
    port.onmessage = (e) => {
        setTimeout(()=>{
            ports.forEach(p=>p.postMessage([e.data, ev.ports.length]));
        },300);
    }
    port.start();
    ports.push(port);
}