与iframe进行跨域通信时,异步/等待应答onMessage事件

Nix*_*n85 5 javascript iframe postmessage promise async-await

我有一个iframe,它正在与其父窗口进行通信以通过postMessage 方法设置和获取一些必要的cookie 。

首先,iframe应用程序中的一个函数从父窗口请求CookieA。

    function requestCommunication(topic, customerId) {

            function cookieAvailable() {
                return new Promise((resolve) => resolve(getCookieData('cookieName'));
                });
            }

            console.log(cookieAvailable());

            if(!!cookieAvailable()) {
                //doStuff        
            }
     }
Run Code Online (Sandbox Code Playgroud)

cookieAvailable()触发从iframe发送到parent.window的消息。依次,窗口返回cookie及其数据作为字符串。通过使用以下操作完成此操作:

 async function getCookieData(cookieName) {

        const {data} = await new Promise(resolve => {

            window.onmessage = (event) => {
                resolve(event);
            }
        });

        var cookieContent = JSON.parse(data);
        var cookieContentData = JSON.parse(cookieContent.data);

        return cookieContentData; //this returns the cookie data (in almost all cases)
    }    
Run Code Online (Sandbox Code Playgroud)

我没有如何正确使用诺言将其移交给我的初始触发功能。我将不胜感激。

Kai*_*ido 5

您的代码中存在明显的问题和反模式。cookieAvailable将返回一个 Promise,因此您的支票if(!!cookieAvailable()) {将始终是真实的。在检查是否确实有可用的 cookie 之前,您需要等待该 Promise 解决。

但实际上,你的cookieAvailable函数没有任何返回 Promise 包装器:如果thisChatClient.cookie.getCookieData返回 Promise 则直接返回它,无需将其包装在 Promise 中。
如果它返回一个同步结果,那么你只能通过将其包装在 Promise 中来释放它

async function requestCommunication(topic, customerId) {

  function cookieAvailable() {
    // this is already a Promise
    return thisChatClient.cookie.getCookieData('sess_au');
  }

  const isCookieAvailable = await cookieAvailable();

  if (!!isCookieAvailable) {

  }
}
requestCommunication().catch(console.error);
Run Code Online (Sandbox Code Playgroud)

现在所有这些都无助于对您的问题做出正确的回答:您的两个代码块之间的链接根本不清楚。

没有任何东西调用这两个函数。

getCookieData将等待一个 MessageEvent,而不让任何人知道它正在等待它。

我不确定您打算如何让您的 iframe 知道它应该向您的窗口发送一条包含此信息的消息,但这是您必须考虑的事情。

但在去那里之前我应该​​注意:尽管它可能很诱人,但将事件包装在 Promise 中通常是一个坏主意。

事件和承诺是不同的东西,后者应该只解决一次,而前者可能会从不同的来源多次触发。

IMM 只有当您确定事件只会触发一次时才最好这样做。对于 MessageEvent,您根本不知道它。
您的用户很可能在其浏览器上安装了一个扩展程序,该扩展程序将使用 postMessage 作为通信方式。如果在所有 iframe 上添加此扩展,则您的代码将被破坏。

相反,您应该检查MessageChannel API,它将为您提供通信方式,您可以确定您将是唯一使用的通信方式。
我认为这个答案不适合解释这个 API 是如何工作的,但是请看一下这个概述,它解释了非常基础的知识。

由于您一定会控制两端,因此您可以从那里建立一个基于 Promise 的系统。

在主页中,您将准备 MessageChannel 对象,并将其发送到 iframe,同时侦听响应。当这个响应到来时,你将能够实现你的 Promise。

在 iframe 中,您将在窗口上添加一个侦听器以捕获 MessageChannelPort。发生这种情况时,您将向服务请求 cookie 并通过 MessageChannel 的端口将其发回。

即使在这次交换期间主窗口上出现一条消息,您也可以确定它不会是您正在等待的消息。

// Sets up a new MessageChannel
// so we can return a Promise
function getCookieData() {
  return new Promise((resolve) => {
    const channel = new MessageChannel();
    // this will fire when iframe will answer
    channel.port1.onmessage = e => resolve(e.data);
    // let iframe know we're expecting an answer
    // send it its own port
    frame.contentWindow.postMessage('getCookie', '*', [channel.port2]);  
  });
}

frame.onload = async e => {
  const frameHasCookie = await getCookieData();
  console.log(frameHasCookie);
};

frame.src = generateFrameSRC();

function generateFrameSRC() {
  // The content of your iframe
  const cont = `
<html>
  <head>
    <script>
      const originClean = "null";
      onmessage = async e => {
        // only if it's the origin we expected 
        // and if it does send a  MessagePort 
        // and the message is "getCookie"
        if(e.origin === originClean && e.ports && e.data === "getCookie") {
          const data = await asyncData();
          // respond to main window
          e.ports[0].postMessage(data);
        }
      };
      function asyncData() {
        return new Promise(resolve => 
          setTimeout(() => resolve("the data"), 1000)
        );
      }
    <\/script>
  </head>
  <body>
    hello
  </body>
</html>`;
  return 'data:text/html,' + encodeURIComponent(cont)
}
Run Code Online (Sandbox Code Playgroud)
<iframe id="frame"></iframe>
Run Code Online (Sandbox Code Playgroud)