62

I'm trying to pass an object to a web worker through the postMessage function.
This object is a square that has a couple of functions to draw himself on a canvas and some other things. The web worker must return an array of this objects.
The problem is that when I call the postMessage function with this object, I get an this error:

Uncaught Error: DATA_CLONE_ERR: DOM Exception 25

I get this both sending the object to the worker and the other way around.
I think the error is because javascript must serialize the object, but can't do it because the object has functions built-in.

Does anyone ever had a similar problem? Do you know some workarround to this?
Thanks in advance.

Kobi
  • 135,331
  • 41
  • 252
  • 292
dgiulian
  • 1,199
  • 2
  • 10
  • 11
  • I agree with your assessment: the browser is trying to serialize the data. It looks like you are working with Firefox. Safari is even worse, it will only take strings. – Hemlock Oct 09 '11 at 17:08
  • No, I'm working with chrome. But I guess all browsers behave somehow similar about this. – dgiulian Oct 10 '11 at 14:10

9 Answers9

56

There are a few reasons why the error that you mention could have been thrown, the reasons are listed here.

When sending objects to web workers, the object is serialized, and later deserialized in the web worker if the object is a serializable object.

This means that the methods for the objects you send to your web worker are not something that can be passed to the web worker (causing the error that you have run into), and you will need to provide the necessary methods/functions to the objects on the web worker's side of the environment, and make sure they are not part of the object that is passed to the web worker(s).

erikvold
  • 15,988
  • 11
  • 54
  • 98
  • 1
    I've tryied sending Arrays, and that worked. I'll try to send some objet without the member functions, and see what happens. Thanks a lot for your help. – dgiulian Oct 10 '11 at 14:09
  • 5
    @Bergi - correct that structured clone algorithm might be employed, but Error and Function objects cannot be duplicated by the structured clone algorithm; attempting to do so will throw a DATA_CLONE_ERR exception. This relates directly back to the OP question, and explains the error in question. – arcseldon May 31 '14 at 02:35
  • 2
    it works when you send objects (objects, array), but not function – Hazarapet Tunanyan Aug 01 '16 at 15:38
  • I'd also recommend using `class-transformer` as a way to restore object methods and prototypes after deserialization, if someone is still looking for the simplest and the most scalable long-term way of handling this. You may already be familiar with this if you've worked with ORMs, since many of them do exactly that after getting data from a database, making it an instance of a class instead of just data. – Art Ginzburg May 30 '23 at 12:07
13

As you suspected objects with functions cannot be posted. The same goes for objects with recursive references, but this has changed in some browsers lately. Instead of risking doing manual and costly redundant serialization for every post you can perform a test at the beginning of your script to determine which functions to use for sending/receiving data.

I've had the same problem and solved it by moving almost all code into the worker and just keeping a renderer (wrapping the 2d context renderer) in the main thread. In the worker I serialize the different draw calls meant for the canvas into just numbers in an (typed) array. This array is then posted to the main thread.

So for instance when I want to draw an image I invoke the drawImage() method on my worker renderer instance in the worker. The call is translated into something like [13,1,50,40] which corresponds to the draw method enum, image unique id and its xy coordinates. Multiple calls are buffered and put in the same array. At the end of the update loop the array is posted to the main thread. The receiving main renderer instance parses the array and perform the appropriate draw calls.

bennedich
  • 12,150
  • 6
  • 33
  • 41
10

I recently encountered this same problem when using web workers. Anything I passed to my worker kept all its properties but mysteriously lost all its methods.

You will have to define the methods in the web worker script itself. One workaround is to importScripts the class definition and manually set the __proto__ property of anything you receive. In my case I wanted to pass a grid object, defined in grid.js (yup, I was working on 2048), and did it like so:

importScripts('grid.js')

onMessage = function(e) {
  e.data.grid.__proto__ = Grid.prototype;
  ...
}
xuanji
  • 5,007
  • 2
  • 26
  • 35
10

When you pass data to a web worker, a copy of the data is made with the structured clone algorithm. It is specified in HTML5 (see § 2.9: Safe passing of structured data).

MDN has an overview of supported types. As functions are not supported, trying to clone objects containing functions will therefore throw a DATA_CLONE_ERR exception.

What to do if you have an object with functions?

  • If the functions are not relevant, try to create a new object that contains only the data that you want to transfer. As long as you use only supported types, send should work. Using JSON.stringify and JSON.parse can also be used as a workaround, as stringify ignores functions.

  • If the functions are relevant, there is no portable way. There are attempts to use a combination of toString and eval (e.g., used by the jsonfs library), but this will not work in all cases. For instances, it will break if your function is native code. Also closures are problematic.

Philipp Claßen
  • 41,306
  • 31
  • 146
  • 239
5

The real problem with object and webworkers is with the methods of that objects. A object should not have methods just properties.

Ex:

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


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

The first wont work (with the mentioned error message) with postMessage and the second will work.

Kemal Fadillah
  • 9,760
  • 3
  • 45
  • 63
José Cabo
  • 6,149
  • 3
  • 28
  • 39
  • @DavidThielen, I have not tried it now but it was a correct behaviour description at the moment of the question. Maybe this behaviour was a bug and now browsers just "filter" this properties with a function as a value (which is the ideal behaviour in my opinion). But in any case it is not possible to pass throw a webworker a function pointer. Anyway and as I said, I cannot confirm it since I have not tried it now ;) – José Cabo Apr 28 '14 at 17:49
2

Some type of objects like ArrayBuffer and ImageBitmap which have the Transferable interface implementet and can be transfered without copy the Object.

Thats very usefull in Context of Canvas + Web worker cause you can save the time of copy the data between the threads.

julian libor
  • 1,583
  • 1
  • 9
  • 8
1

take a look at the vkThread plugin

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

it can pass function to a worker, including function with context ( object's method ). It can also pass functions with dependencies, anonymous functions and lambdas.

--Vadim

vadimk
  • 1,461
  • 15
  • 11
0

Another way of handling this (as I come across this question a decade later having needed to do it myself) is to define a static clone() function on your class that constructs a new object from (the properties of) an old one; then you can simply say

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

at the start of your worker to get a 'real' object of type MyClass that you can then call methods on from within your worker.

-2

if you want to pass the object with methods you can stringify it and parse it at the receiving end.

postMessage(JSON.stringify(yourObject)

In the listener

this.worker.addEventListener('message', (event) => {
   const currentChunk = JSON.parse(event.data);   
});