首页 > 解决方案 > Async/Await 在与 iframe 的跨域通信中回答 onMessage 事件

问题描述

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

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

    function requestCommunication(topic, customerId) {

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

            console.log(cookieAvailable());

            if(!!cookieAvailable()) {
                //doStuff        
            }
     }

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)
    }    

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

标签: javascriptiframepromiseasync-awaitpostmessage

解决方案


您的代码中存在明显的问题和反模式。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);

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

什么都不调用函数。

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

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

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

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

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)
}
<iframe id="frame"></iframe>


推荐阅读