Передача объектов веб-работнику

Я пытаюсь передать объект веб-работнику через функцию postMessage.
Этот объект представляет собой квадрат, в котором есть пара функций для рисования на холсте и некоторых других вещах. Веб-работник должен вернуть массив этих объектов.
Проблема в том, что когда я вызываю функцию postMessage с этим объектом, я получаю эту ошибку:

Uncaught Error: DATA_CLONE_ERR: DOM Exception 25

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

У кого-нибудь когда-нибудь была подобная проблема? Вы знаете какой-то обходной путь к этому?
Заранее спасибо.

9 ответов

Решение

Есть несколько причин, по которым указанная вами ошибка могла быть выдана, причины перечислены здесь.

При отправке объектов веб-работникам объект сериализуется, а затем десериализуется в веб-работнике, если объект является сериализуемым объектом.

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

Как вы подозревали, объекты с функциями не могут быть опубликованы. То же самое касается объектов с рекурсивными ссылками, но в последнее время это изменилось в некоторых браузерах. Вместо того, чтобы рисковать делать ручную и дорогостоящую избыточную сериализацию для каждого сообщения, вы можете выполнить тест в начале вашего сценария, чтобы определить, какие функции использовать для отправки / получения данных.

У меня была та же проблема, и я решил ее, переместив почти весь код в рабочий и просто сохранив рендерер (оборачивая 2d-контекстный рендер) в основной поток. В работнике я сериализирую различные вызовы отрисовки, предназначенные для холста, в просто числа в (типизированном) массиве. Затем этот массив публикуется в главном потоке.

Так, например, когда я хочу нарисовать изображение, я вызываю drawImage() метод на моем рабочем экземпляре рендерера в рабочем. Звонок переводится в нечто вроде [13,1,50,40] что соответствует enum метода рисования, уникальному идентификатору изображения и его координатам xy. Несколько вызовов буферизуются и помещаются в один массив. В конце цикла обновления массив публикуется в основном потоке. Принимающий экземпляр основного средства визуализации анализирует массив и выполняет соответствующие вызовы отрисовки.

Когда вы передаете данные веб-работнику, копия данных создается с помощью алгоритма структурированного клонирования. Это указано в HTML5 (см. § 2.9: Безопасная передача структурированных данных).

MDN имеет обзор поддерживаемых типов. Поскольку функции не поддерживаются, попытка клонировать объекты, содержащие функции, будет DATA_CLONE_ERR исключение.

Что делать, если у вас есть объект с функциями?

  • Если функции не актуальны, попробуйте создать новый объект, содержащий только те данные, которые вы хотите перенести. Пока вы используете только поддерживаемые типы, отправка должна работать. С помощью JSON.stringify а также JSON.parse может также использоваться в качестве обходного пути, так как stringify игнорирует функции.

  • Если функции актуальны, переносного способа нет. Есть попытки использовать комбинацию toString а также eval (например, используется библиотекой jsonfs), но это работает не во всех случаях. Например, он сломается, если ваша функция будет нативным кодом. Также закрытие проблематично.

Я недавно столкнулся с этой же проблемой при использовании веб-работников. Все, что я передавал своему работнику, сохраняло все его свойства, но загадочным образом теряло все свои методы.

Вы должны будете определить методы в самом скрипте веб-работника. Одним из обходных путей является importScripts определение класса и вручную установить __proto__ свойство всего, что вы получаете. В моем случае я хотел пройти grid объект, определенный в grid.js (да, я работал на 2048), и сделал это так:

importScripts('grid.js')

onMessage = function(e) {
  e.data.grid.__proto__ = Grid.prototype;
  ...
}

Настоящая проблема с объектами и веб-работниками заключается в методах этих объектов. У объекта не должно быть методов, только свойства.

Пример:

var myClass = function(){
    this.a = 5;
    this.myMethod = function(){}
}
var notParseableObject = new myClass();


var myClass2 = function(){
    this.a = 5;
}
var parseableObject = new myClass2();

Первый не будет работать (с упомянутым сообщением об ошибке) с postMessage, а второй будет работать.

Взгляните на плагин vkThread

http://www.eslinstructor.net/vkthread/

он может передавать функцию работнику, включая функцию с контекстом (метод объекта). Он также может передавать функции с зависимостями, анонимными функциями и лямбдами.

--Vadim

Некоторые типы объектов, такие как ArrayBuffer и ImageBitmap, которые имеют реализуемый интерфейс Transferable и могут передаваться без копирования Object.

Это очень полезно в контексте Canvas + Web работник, потому что вы можете сэкономить время копирования данных между потоками.

Другой способ справиться с этим (поскольку я столкнулся с этим вопросом десять лет спустя, когда мне нужно было сделать это самостоятельно) - определить статический clone()функция вашего класса, которая создает новый объект из (свойств) старого; тогда вы можете просто сказать

      MyClass cloneObj = MyClass.clone(evt.data.myObj);

в начале вашего работника, чтобы получить «настоящий» объект типа MyClass, для которого вы можете затем вызывать методы из своего работника.

Если вы хотите передать объект методами, вы можете преобразовать его в строку и проанализировать на принимающей стороне.

postMessage(JSON.stringify(yourObject)

В слушателе

this.worker.addEventListener('message', (event) => {
   const currentChunk = JSON.parse(event.data);   
});
Другие вопросы по тегам