A brief discussion on MessageChannel

What is MessageChannel

MessageChannel allows two different scripts running in different browser contexts of the same document (such as two iframes, the document body and an iframe, using SharedWorkertwo documents, or two workers) to communicate directly, using a port on each end (port) delivers messages to each other through a two-way channel (channel).

MessageChannel 

DOM Eventsends messages in the form of , so it is an asynchronous macro task.

Basic usage

  1. Use MessageChannel()the constructor to create a communication channel and obtain two port MessagePort objects port1 port2;
  2. One port is used to postMessagesend messages, and the other port is used to onmessagereceive messages;
  3. Another port onmessagereceives messages via;
  4. Handling is used when the port receives a message that cannot be deserialized onmessageerror;
  5. When stopping sending messages, call closeclose port;

method one

const { port1, port2 } = new MessageChannel();
port1.onmessage = ( event ) => {
   console.log ( ' Received message from port2:' , event.data ) ;
};
port1.onmessageerror = (event) => {};

port2.onmessage = function ( event ) {
   console.log ( ' Message received from port1:' , event.data ) ;
  port2. postMessage ( 'I am port2' );
};
port2.onmessageerror = (event) => {};

port1. postMessage ( 'I am port1' );

Method 2

const { port1, port2 } = new MessageChannel();
port1. addEventListener ( 'message' , event => {
   console . log ( 'Message received from port2: ' , event. data );
});
port1.addEventListener('messageerror', (event) => { });
port1.start();

port2. addEventListener ( 'message' , event => {
   console . log ( 'Received message from port1: ' , event. data );
  port2. postMessage ( 'I am port2' );
});
port2.addEventListener('messageerror', (event) => { });
port2.start();

port1. postMessage ( 'I am port1' );

The output of the above two methods is:

Received message from port1: I am port1
Received message from port2: I am port2

  • In terms of usage addEventListener, you need to manually call start()the method for messages to flow, because it is paused during initialization.
  • onmessageThe method has been called implicitly start().

Execution order in Event Loop

Synchronous tasks > Micro tasks > requestAnimationFrame > DOM rendering > Macro tasks

setTimeout(() => {
    console.log('setTimeout')
}, 0)

const { port1, port2 } = new MessageChannel()
port2.onmessage = e => {
    console.log(e.data)
}
port1.postMessage('MessageChannel')

requestAnimationFrame(() => {
    console.log('requestAnimationFrame')
})

Promise.resolve().then(() => {
    console.log('Promise1')
})

The output is:

Promise  // Microtask is executed first
requestAnimationFrame
setTimeout  // Macro task, define it first and execute it first 
MessageChannel  // Macro task, define it and execute it later

requestAnimationFrame – a task that is not a macrotask

window.requestAnimationFrame() tells the browser that you want to perform an animation and requires the browser to call the specified callback function to update the animation before the next redraw. This method requires passing in a callback function as a parameter, which will be executed before the next redraw of the browser — MDN

Strictly speaking, raf is not a macro task because

  • The execution timing is completely inconsistent with the macro task;
  • When the raf task queue is executed, all tasks in the queue at this moment will be executed;

scenes to be used

1: Context communication of the same document

var channel = new  MessageChannel ();
 var para = document . querySelector ( 'p' );

var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;

ifr.addEventListener("load", iframeLoaded, false);

function iframeLoaded() {
  otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}

channel.port1.onmessage = handleMessage;
function handleMessage(e) {
  Para. innerHTML = e. data ;
}

Two: Combined with Web Worker to achieve multi-threaded communication

Three: Deep copy

Use it in most scenes that require deep copying JSON.parse(JSON.stringify(object)). But this method will ignore undefined, function, symbol and circular reference objects .

// Deep copy function 
function  deepClone ( val ) {
   return  new  Promise ( resolve => {
     const { port1, port2 } = new  MessageChannel ()
    port2.onmessage = e => resolve(e.data)
    port1.postMessage(val)
  })
}

Deep copy implemented using MessageChannel can only solve the problems of undefined and circular reference objects, but it is still helpless for Symbol and function.

practice

Problem description :
There are two methods called by a third party in an uncertain order. After both methods are called, further processing will be performed.

The solution
uses setTimeout to simulate the calling sequence of methods. The code is as follows:

const { port1, port2 } = new MessageChannel()
let data

const handleUser = newData => {
if (data) {
const result = { ...data, ...newData }
console.log('获取到全部数据', result)
port1.close()
port2.close()
} else {
data = newData
}
}

const getName = () => {
const params = { name: '123' }
port1.postMessage(params)
port1.onmessage = e => {
handleUser(e.data)
}
}
const getAge = () => {
const params = { age: 88 }
port2.postMessage(params)
port2.onmessage = e => {
handleUser(e.data)
}
}

setTimeout(() => {
getName()
}, 0)

setTimeout(() => {
getAge()
}, 10)