循环发送消息

Postmessage in a loop

我想用 1M 消息轰炸接收者,而且接收者将尽快收到每条消息。天真的方法是循环 1M 次,然后只用 postmessage 向接收者发送一条消息。没用。

我得到的是整个 1M 消息都在排队,只有当代码完成时,接收方才开始处理它们。

我需要做的是发件人将发送 1M 条消息,并且当他继续发送消息时,接收方将同时处理它们。

比如我现在的是这样的:

  1. 发件人:发送m1。
  2. 发件人:发送m2。
  3. 发件人:发送 m3。
  4. 接收方:接收到 m1。
  5. 接收方:收到 m2。
  6. 接收方:接收到 m3。

我想要的:

  1. 发件人:发送m1。
  2. 接收方:接收到 m1。
  3. 发件人:发送m2。
  4. 接收方:收到 m2。
  5. 发件人:发送 m3。
  6. 接收方:接收到 m3。

我怎样才能做到这一点?我不能让接收者发送确认。我的目标是以最快的速度发送尽可能多的信息。

编辑:我现在的代码:

Sender:
function sendx(x){
    console.log("start spam");
    for(let i=0; i<200000; i++){
     window.opener.postMessage(x, '*');
    }
    console.log("done");
}


Receiver:
window.addEventListener("message", r_function );
function r_function(event)
{
    let index = event.data;
    let junk = something(index);
    return junk;
}

发送方是接收方创建的新window。我在实践中得到的是,只有当 'sendx' 函数结束时,接收方才开始接收消息。

What i need to happen is that the sender will send 1M messages and as he keeps on sending the messages the receiver simultaneously will process them.

这已经发生了。

const worker = new Worker(URL.createObjectURL(
  new Blob([worker_script.textContent])
 ));
let logged_first = false;
worker.onmessage = e => {
  if(e.data === "spam") {
    if(!logged_first) {
      console.log('received first message at', new Date().toLocaleString());
      logged_first = true; // ignore next messages
    }
  }
  else {
    console.log(e.data);
  }
}
<script type="text/worker-script" id="worker_script">
  const now = performance.now();
  postMessage("start spamming at " + new Date().toLocaleString());
  while(performance.now() - now < 5000) {
    postMessage('spam');
  }
  postMessage("end spamming at " + new Date().toLocaleString());
</script>

但是,要让它发挥作用,需要满足一个重要条件:
您的两个 JavaScript 实例发送方 & 接收方必须运行 在不同的线程上。

也就是说,如果您在同一线程上使用 MessageChannel 执行此操作,那么它显然无法在发送消息的同时处理消息:

const channel = new MessageChannel();

channel.port1.onmessage = e => {
  console.log('received first message at', new Date().toLocaleString());
  channel.port1.onmessage = null; // ignore next messages
};

const now = performance.now();
console.log("start spamming at ", new Date().toLocaleString());

while(performance.now() - now < 5000) {
  channel.port2.postMessage('spam');
}

console.log("end spamming at ", new Date().toLocaleString());

如果您正在处理 iframe 或其他 window,您不能确定您是否满足此条件。 浏览器的行为各不相同,在这里,但他们都会 运行 至少一些 windows 在同一个过程中。您无法控制将使用哪个进程,因此不能保证您会 运行 在另一个进程中。

所以你能做的最好的事情就是运行你的循环在一个定时循环中,这会让浏览器有一些空闲时间来处理其他windows事件循环正确。
我们拥有的最快的定时循环实际上是 postMessage 提供给我们的。 因此,要按照您的意愿行事,最好是 运行 在 MessageChannel 对象的 message 事件中循环的每次迭代。

为此,ES6中引入的generator function*很有用:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
  messages.push(e.data);
};
!function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

// make our loop a Generator function
function* ourLoopGen(i) {
  while(i++ < 1e6) {
    opener.postMessage(i);
    yield i;
  }
}
const ourLoop = ourLoopGen(0);

// here we init our time-loop
const looper = new MessageChannel();
looper.port2.onmessage = e => {
  const result = ourLoop.next();
  if(!result.done)
    looper.port1.postMessage(''); // wait next frame
};
// start our time-loop
looper.port1.postMessage('');
<pre id="log"></pre>

我们也可以使用 ES6 async/await syntax 做同样的事情,因为我们可以确定我们的 MessageChannel 驱动的定时循环中没有其他东西会干扰(不像 Window 的 postMessage),我们可以承诺:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
const fake_win = new MessageChannel();
const win = fake_win.port1; // window.open('your_url', '')
const opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
const messages = [];
win.onmessage = e => {
  messages.push(e.data);
};
! function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

const looper = makeLooper();
// our async loop function
async function loop(i) {
  while (i++ < 1e6) {
    opener.postMessage(i);
    await looper.next()
  }
}
loop(0);

// here we init our promisified time-loop
function makeLooper() {
  const engine = new MessageChannel();
  return {
    next() {
      return new Promise((res) => {
        engine.port2.onmessage = e => res();
        engine.port1.postMessage('');
      });
    }
  };
};
<pre id="log"></pre>

但它显然也可以完全采用带有回调和一切的 ES5 风格:

/***************************/
/* Make Fake Window part   */
/* ONLY for DEMO           */
/***************************/
var fake_win = new MessageChannel();
var win = fake_win.port1; // window.open('your_url', '')
var opener = fake_win.port2; // used in Receiver

/********************/
/* Main window part */
/********************/
var messages = [];
win.onmessage = function(e) {
  messages.push(e.data);
};
!function log_msg() {
  document.getElementById('log').textContent = messages.length;
  requestAnimationFrame(log_msg);
}();

/*******************/
/* Receiver part   */
/*******************/

var i = 0;
var looper = makeLooper(loop);
// our callback loop function
function loop() {
  if (i++ < 1e6) {
    opener.postMessage(i);
    looper.next(loop);
  }
}
loop(0);

// here we init our promisified time-loop
function makeLooper(callback) {
  var engine = new MessageChannel();
  return {
    next: function() {
      engine.port2.onmessage = function(e) {
        callback();
      }
      engine.port1.postMessage('');
    }
  };
};
<pre id="log"></pre>

但请注意,浏览器无论如何都会限制未聚焦的页面,因此您的结果可能比这些片段中的结果慢。