PostMessage с несколькими функциями или настраиваемыми обратными вызовами

До сих пор я видел только учебники для пост-сообщений, в которых одно окно отправляет сообщение одного типа, а другое окно интерпретирует сообщение только одним способом.

Что, если я хочу иметь много разных видов взаимодействия между окнами, сможет ли это обработать postmessage?

Это идет вразрез с тем, что должно делать пост-сообщение?

Например, что, если я хочу иметь возможность отправлять пользовательские обратные вызовы туда и обратно и т. Д.?


person johnnietheblack    schedule 12.12.2011    source источник
comment
Не уверен, что вы имеете в виду; вы можете проверить полученное сообщение и условно вызвать другой код и т. д., так что ...   -  person Pointy    schedule 12.12.2011
comment
Просто до сих пор я не видел ни одного примера этого, и, судя по тому, что я читал, пост-сообщение не может отправлять объекты или что-то еще, а это означает, что мне пришлось бы разрезать строку ... просто не не кажется очень чистым.   -  person johnnietheblack    schedule 13.12.2011


Ответы (4)


Есть несколько способов передать сообщение, состоящее из нескольких частей, обработчику postMessage. Первый (и менее «чистый» способ) - использовать символ-разделитель, а затем передавать данные через строку.

Допустим, мы хотели передать идентификатор пользователя, действие и имя пользователя. Строка будет выглядеть так:

54|do_logout|chris

В обработчике postMessage переданные данные могут быть split (docs) на символе | , то каждый сегмент сообщения можно использовать по мере необходимости.

Другой путь, вместо создания / разделения строки вручную, заключается в использовании JSON (docs) для преобразовать объект в строку с одной стороны и использовать JSON для обратного преобразования в объект в обработчике.

var pass_data = {
    'name':'Chris',
    'id':54,
    'action':'do_logout'
};
target_window.postMessage(JSON.stringify(pass_data), "http://www.example.net");

... затем в обработчике:

function (event) {
    var pass_data = JSON.parse(event.data);
}

Не забудьте, однако, протестировать, поскольку объект JSON предоставляется не во всех пользовательских агентах, особенно в старых. Существует много (много, много) сторонних библиотек для ограничения поддержки JSON, так что пусть вас не пугает отсутствие полного принятия - JSON определенно является безопасным стандартом для «продвижения вперед».

Разве не было бы лучше, если бы мы могли сразу пройти мимо этого объекта? Что ж, глядя в Firefox 6 (источник), данные, которые вы передаете обработчику пост-сообщения, могут быть объектом . Объект будет сериализован, поэтому есть некоторые проблемы на этом фронте, но:

var pass_data = {
    'name':'Chris',
    'id':54,
    'action':'do_logout'
};
target_window.postMessage(pass_data, "http://www.example.net");

Немного лучше, а? К сожалению, текущие версии IE работают только со строками. Мне не удалось найти обсуждения будущих планов относительно postMessage для IE 10. Кроме того, в IE 8/9 есть известная ошибка, которая нарушает postMessage что-либо, кроме фреймов. (источник).

Переходя к конкретному аспекту вашего вопроса - обратным вызовам. Если вы не можете передать обратный вызов по имени функции, нет способа передать функцию; никаких анонимных функций для вас. Это связано с тем, как данные фактически передаются обработчику. На практике «не существует» поддержки объектов как данных, за кулисами браузер превращает переданный объект в строку (сериализация).

Таким образом, вы должны понимать, что передача объекта в точности такая же, как использование JSON для stringify объекта перед передачей, только в первом случае браузер выполняет собственную сериализацию (и последующую десериализацию), тогда как во втором маршруте Вам решать сериализовать / десериализовать.

Пункты приема пищи здесь:

  • postMessage по-прежнему имеет ограниченную кроссбраузерную поддержку
  • Тенденция к появлению новых версий браузеров, соответствующих стандартам, заключается в том, чтобы разрешить прохождение объектов в дополнение к строкам.
  • Переданный объект будет сериализован, поэтому ссылки на функции не допускаются.
  • Самая широкая поддержка «в дикой природе» предназначена для данных, состоящих только из строк, что означает, что вам придется придерживаться строк и «упаковать» свои данные, как показано выше, если вы хотите поддерживать широкий спектр пользовательских агентов.
  • Internet Explorer испортит любой план, который вы когда-либо составляли (включая семейный отдых)

Документация и справочные материалы

person Chris Baker    schedule 12.12.2011
comment
Отличный ответ! Хотел бы я +10 за комментарий к семейным праздникам - person johnnietheblack; 13.12.2011
comment
Вы всегда можете выполнить кодирование и декодирование JSON с любой стороны сообщения. - person Pointy; 13.12.2011
comment
Даже с сериализацией JSON вы не можете передавать ссылки на функции (которые, как я полагаю, был после OP), поскольку они не могут быть сериализованы. Это так, как если вы JSON кодируете объект (для IE) или просто передаете объект (для Firefox ›6). Если вы попытаетесь передать функцию в Firefox, вы получите ошибку Error: The object could not be cloned. :( Жаль, но, возможно, так безопаснее. - person Chris Baker; 13.12.2011
comment
Ну, строго говоря, вы никогда не можете передавать функции с помощью JSON, по крайней мере, напрямую. - person Pointy; 13.12.2011
comment
Отличный, подробный ответ. Два больших пальца вверх! - person Hatchmaster; 16.08.2012
comment
Что касается обратных вызовов, вызывающий может передать цели json (строковый) следующим образом: {..., onSuccess:"functionNameOnSuccess", onError: "functionNameOnError"}, а затем цель может ответить вызывающему другому сообщению, например: {callbackMsg: "functionNameOnSuccess('this is a response')"} Затем вызывающему абоненту просто нужно сделать что-то вроде: eval(json.callbackMsg) Это, вероятно, сработает, и это что-то вроде обратного вызова, о котором я могу думать. - person Joao Sousa; 02.05.2013
comment
Более новые версии postMessage также могут передавать фактические объекты, но добавляя объекты для выпуска в качестве третьего параметра. Это означает, что окно отправки больше не может получить доступ к объектам, но обеспечивает быструю передачу сообщений с нулевым копированием. - person user239558; 01.01.2014

Обратные вызовы с postMessage: очень возможно и очень полезно

На npm я нашел хороший плагин под названием "silver-bullet". Он выполняет postMessage с обратными вызовами и использует eventEmitter для получения определенных событий. Это очень приятно.

Но для реализации этого я бы сделал что-то вроде ...

phostMessage(iframe, someObj, callback);

Вы должны сделать это:

  1. Вам нужен общий идентификатор обратного вызова, передаваемый между кадрами, обменивающимися данными.
  2. Отправитель создает уникальный идентификатор обратного вызова для каждого сообщения и сохраняет его в поисковом хэше обратного вызова, чтобы найти обратный вызов после отправки.
  3. Получатель сообщения только обеспечивает отправку идентификатора обратного вызова.
  4. Все взаимодействующие фреймы используют для этого одну и ту же библиотеку JS.

Вот простая демонстрация этого:

var callbacks = {};

// when receiving messages
window.addEventListener('message', function(ev) {
  // todo: add origin check
  if (!ev.data)
    return;

  var message;
  try {
    message = JSON.parse(ev.data);
  } catch (ex) {
    console.error(ex);
  }

  // ignore messages not having a callback ID
  if (!message || !message.callbackId)
    return;

  // we are the sender getting the callback
  if (callbacks[message.callbackId]) {
    callbacks[message.callbackId](message);
    delete callbacks[message.callbackId];
    return;
  }

  // we are the receiver so we respond with the callback ID
  // todo: restrict who can receive message (last param)
  iframe.contentWindow.postMessage(JSON.stringify(message), '*');
});

// when sending messages
function phostMessage(iframe, obj, callback) {
  obj.eventId = Math.random();
  callbacks[obj.eventId] = callback;
  // todo: restrict who can receive message (last param)
  iframe.contentWindow.postMessage(JSON.stringify(obj), '*');
}

Я немного углублю эту концепцию и использую поиск обработчика сообщений, в котором сообщение имеет желаемое имя функции обработчика, чтобы вызвать и передать сообщение. Обработчик сообщений также принимает обратный вызов, который по завершении запускает обратный вызов. Обратный вызов просто имеет простую логику повторного вызова собственного почтового сообщения, отправляющего обратно его полученный идентификатор обратного вызова.

Итак, последняя строка кода для обработки события сообщения будет следующей:

if (messageHandler[message.handler])
  messageHandler[message.handler](message, function() {
    iframe.contentWindow.postMessage(JSON.stringify(message), '*');
  });
else
  iframe.contentWindow.postMessage(JSON.stringify(message), '*');

что позволяет происходить асинхронным вещам.

person King Friday    schedule 25.06.2015
comment
В настоящее время для абстракции postMessage доступны лучшие и более популярные библиотеки. Ознакомьтесь с jschannel или напарник - person Radek Matěj; 02.05.2018
comment
@ RadekMatěj согласился. Это было здесь для просвещения, не более того. Хорошее предложение. - person King Friday; 02.05.2018

Недавно я столкнулся с той же проблемой. После нескольких часов поиска я наткнулся на пост-робота. Он разработан paypal и решил большинство моих проблем, включая обратный вызов для postMessage.

Он также поддерживает передачу функций внутри полезной нагрузки.

Вы можете ознакомиться с введением здесь Представляем пост-робота

person Atishay Jain    schedule 19.03.2019

Один довольно простой способ запускать обратные вызовы без передачи фактического кода:

Цель

var callbacks = {
  myCallback: function() { doSomething(); }
};
window.addEventListener('message', function (ev) {
  // origin checking etc
  callbacks[ev.data]();
}, false);

Источник

target.postMessage('myCallback', 'http://www.example.com');
person Jacob Rask    schedule 03.08.2012
comment
На самом деле это не решает проблему. Это просто обратный вызов на стороне получателя. Вопрос в том, чтобы передать обратный вызов как часть сообщения, чтобы вы могли отреагировать, например, на успешное выполнение запроса. - person Radek Matěj; 02.05.2018