循环发送消息
Postmessage in a loop
我想用 1M 消息轰炸接收者,而且接收者将尽快收到每条消息。天真的方法是循环 1M 次,然后只用 postmessage 向接收者发送一条消息。没用。
我得到的是整个 1M 消息都在排队,只有当代码完成时,接收方才开始处理它们。
我需要做的是发件人将发送 1M 条消息,并且当他继续发送消息时,接收方将同时处理它们。
比如我现在的是这样的:
- 发件人:发送m1。
- 发件人:发送m2。
- 发件人:发送 m3。
- 接收方:接收到 m1。
- 接收方:收到 m2。
- 接收方:接收到 m3。
我想要的:
- 发件人:发送m1。
- 接收方:接收到 m1。
- 发件人:发送m2。
- 接收方:收到 m2。
- 发件人:发送 m3。
- 接收方:接收到 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>
但请注意,浏览器无论如何都会限制未聚焦的页面,因此您的结果可能比这些片段中的结果慢。
我想用 1M 消息轰炸接收者,而且接收者将尽快收到每条消息。天真的方法是循环 1M 次,然后只用 postmessage 向接收者发送一条消息。没用。
我得到的是整个 1M 消息都在排队,只有当代码完成时,接收方才开始处理它们。
我需要做的是发件人将发送 1M 条消息,并且当他继续发送消息时,接收方将同时处理它们。
比如我现在的是这样的:
- 发件人:发送m1。
- 发件人:发送m2。
- 发件人:发送 m3。
- 接收方:接收到 m1。
- 接收方:收到 m2。
- 接收方:接收到 m3。
我想要的:
- 发件人:发送m1。
- 接收方:接收到 m1。
- 发件人:发送m2。
- 接收方:收到 m2。
- 发件人:发送 m3。
- 接收方:接收到 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>
但请注意,浏览器无论如何都会限制未聚焦的页面,因此您的结果可能比这些片段中的结果慢。