Passing a Javascript-Object to Web Worker - javascript

I have an Object like that:
function A(id) {
this.id = id;
}
A.prototype.getId = function() {
return this.id;
}
It is included in a html-page as a file ("objects.js") as well as in the web worker with importScript("objects.js").
Now I create an instance of A in the html-page with "var a = new A()" and post it with "postMessage()" to a Web Worker.
The funny thing is that in the worker it still has the property "id" with its value but the prototype function is lost.
I guess the reason may be that the prototype functions are "bound" to the html-page context and not to the web worker context.
So what I'm doing in the worker is that:
event.data.a.__proto__ = A.prototype;
It's working and I see it as some kind of cast...
Now my question is if that is the only and a valid way or if there's a better way of posting an object with prototype functions to a web worker.
The object definition is available in both contexts...

The structure clone algorithm that is used for serializing data before sending it to the web worker does not walk the prototype chain (for details, see § 2.7.5 Safe passing of structured data). That explains why the derived functions are not preserved.
Beside manually restoring the object as you did, you could also creating a new object, which has the prototype functions, and use Object.assign to copy the properties from the received object.
Note that both workarounds assume that the prototype object and their functions are known to the web worker. In general, there is no automated way to transfer arbitrary objects while preserving functions (see my answer to this related question about sending objects with functions).

The specification for webworkers does not allow for anything but strings to be passed.
Here is a question about this.
So you should serialize the object data with (for example) json and then deserialize it on the other side, and thus creating a new instance of the object, with the same data, inside the webworker.
The same method can be used to pass the object back out again - but both of them must know how to create, serialize and deserialize object of type A.

Related

How to serialize / deserialize a FileSystemHandle JavaScript object with Dart in IndexedDB storage?

I'm using js-interop to use the File System Access API with Dart in a web environment.
I would like to store a FileSystemDirectoryHandle in IndexedDB to reuse it later.
When storing an instance of FileSystemDirectoryHandle in IndexedDB, the data is a Dart object (Symbol when exploring the value in DevTools). When I read back, value is not a FileSystemDirectoryHandle and all informations of the object is lost and useless.
I did not find a way to store a handle into IndexedDB and read it back as a FileSystemDirectoryHandle.
Below is parts of the code I declare with js-interop:
// Only to keep track of API's types definitions.
typedef Promise<T> = dynamic;
// FileSystemHandle and *Options are declared in the same way.
#JS()
class FileSystemDirectoryHandle extends FileSystemHandle {
external Promise<FileSystemFileHandle> getFileHandle(String name, [FileSystemGetFileOptions? options]);
external Promise<FileSystemDirectoryHandle> getDirectoryHandle(String name, [FileSystemGetDirectoryOptions? options]);
external Promise<void> removeEntry(String name, [FileSystemRemoveOptions? options]);
external Promise<List<String>?> resolve(FileSystemHandle possibleDescendant);
}
Here is what I'm trying to achieve:
final handle = await js.promiseToFuture(window.showDirectoryPicker());
// Storage use dart:indexed_db in a homebrew implementation (tested and works fine with
// primitive types).
await storage.set("dir", handle);
// Reload page...
// Dynamic only
final directory = await storage.get("dir");
print(directory.name);
// Typed
FileSystemDirectoryHandle dirHandle = directory as FileSystemDirectoryHandle;
print(dirHandle.name);
Calling dynamic directory.name throws a NoSuchMethodError:
Uncaught (in promise) Error: NoSuchMethodError: 'name'
method not found
Receiver: Instance of 'LinkedMap<dynamic, dynamic>'
Calling typed dirHandle.name throws an Error:
Error: Expected a value of type 'FileSystemDirectoryHandle', but got one of type 'LinkedMap<dynamic, dynamic>'
Screenshot of IndexedDB in DevTools, when storing from Dart, and storing from JavaScript
My understanding of js-interop is that it translates JavaScript object into a Dart proxy. As I'm sending a Dart object, it does not serialize the JavaScript object, but the Dart proxy object. Therefore the JavaScript object is lost in the process.
Is there a way to pass the JavaScript object to IndexedDB from Dart? Or at least serialize the native JavaScript object from Dart proxy and then send it into IndexedDB?
Any guidance would be much appreciated.
Issue on dart-lang/sdk repository #50621
More on this repository, usage in example here, and js-interop here.
It will throw a NoSuchMethodError as the instance returned from IndexedDB is not a FileSystemDirectoryHandle.
Are you saying directory.name throws a NoSuchMethodError? What is the static type of directory here? Is it possible you need to cast the result to storage.set to FileSystemDirectoryHandle?
My understanding of js-interop is that it translates JavaScript object into a Dart proxy. As I'm sending a Dart object, it does not serialize the JavaScript object, but the Dart proxy object. Therefore the JavaScript object is lost in the process.
This shouldn't be the case. The object is still a JavaScript object, you're just using a Dart type to interact with that object. So, you can use the Dart members you declared for it, but we don't wrap that object if you just write a #JS() interface for it.
A workaround is available in issue linked in original post.
Problem comes from dart:indexed_db package.

Passing objects to a web worker

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

How to preserve the state of JavaScript closure?

I am working on a migration platform to migrate web applications from a device to another. I am extending it to add the support for preserving JavaScript state.
My main task is to create a file representing the current state of the executing application, to transmit it to another device and to reload the state in the destination device.
The basic solution I adopted is to navigate the window object and to save all its descendant properties using JSON as base format for exportation and extending it to implement some features:
preserving object reference, even if cyclic (dojox.json.ref library)
support for timers
Date
non-numericproperties of arrays
reference to DOM elements
The most important task I need to solve now is exportation of closures. At this moment I didn't know how to implement this feature.
I read about the internal EcmaScript property [[scope]] containing the scope chain of a function, a list-like object composed by all the nested activation context of the function. Unfortunately it is not accessible by JavaScript.
Anyone know if there is a way to directly access the [[scope]] property? Or another way to preserve the state of a closure?
This sounds like an impossible feat as you would need access to the references stored in each variable.
The best solution would probably be to first refactor your code into storing state on an available object - that way you could easily use JSON.stringify/parse to save/restore it.
So go from
var myFuncWithScope = (function() {
var variable = 0;
return function() {
return variable++;
}
})();
var serializedState = .... // no can do
to
var state = {
myScope = {
variable: 0
}
};
var myFuncWithoutScope = function(){
return state.myScope.variable++;
}
var serializedState = JSON.stringify(state);
From where are you executing? If you are a native app or web browser extension you may have some hope, via internal access to whichever scripting engine it's using. But from a script in web content, there is no hope.
[[Scope]] is one ECMAScript internal property that you cannot access or preserve from inside the interpreter, but far from the only one; almost all of the [[...]] properties are not accessible. Function code references, prototypes, properties, enumerability, owner context, listeners, everything to do with host objects (such as DOM nodes)... there are infinitely many ways to fail.
You can't preserve or migrate web applications without requiring them to follow some strict rules to avoid all but the most basic JS features.

Using an ActiveX control from another ActiveX control on a web page

I'm having trouble calling a non IDispatch method in an ActiveX control that I've written.
I have a web page with two separate Active X object both of which I've written. I start by calling a method on the first object which returns an interface pointer to a new COM object that is not co-creatable. I then call a method on this new object passing the second ActiveX object as the argument. Inside this method I call QueryInterface to obtain a private/internal interface pointer on my second ActiveX object. The problem is that the returned pointer from QueryInterface is not a valid pointer to my object, and any attempt to use it crashes.
How can I obtain a interface to my actual object that I can use? My private interface uses structures so it not compatible with IDispatch, and being an internal interface I do not desire to expose it at all in the type library.
It's a little hard to tell with just a description, but I assume that the method on the first object is returning an IDispatch pointer to the object it creates? The JScript environment will only be able to cope with that.
Also, is your implementation of QueryInterface valid? Does it work for you in non-scripting contexts?
I am still a little unclear on which objects have which interfaces, but if you have an object with an IDispatch-unfriendly interface, perhaps you can build a simple wrapper object to hold it, where the wrapper object has a proper IDispatch interface?
Apologies if I am way off the mark...I haven't wrestled with ActiveX stuff in a few months.

How do you free a wrapped C++ object when associated Javascript object is garbage collected in V8?

V8's documentation explains how to create a Javascript object that wraps a C++ object. The Javascript object holds on to a pointer to a C++ object instance. My question is, let's say you create the C++ object on the heap, how can you get a notification when the Javascript object is collected by the gc, so you can free the heap allocated C++ object?
The trick is to create a Persistent handle (second bullet point from the linked-to API reference: "Persistent handles are not held on a stack and are deleted only when you specifically remove them. ... Use a persistent handle when you need to keep a reference to an object for more than one function call, or when handle lifetimes do not correspond to C++ scopes."), and call MakeWeak() on it, passing a callback function that will do the necessary cleanup ("A persistent handle can be made weak, using Persistent::MakeWeak, to trigger a callback from the garbage collector when the only references to an object are from weak persistent handles." -- that is, when all "regular" handles have gone out of scope and when the garbage collector is about to delete the object).
The Persistent::MakeWeak method signature is:
void MakeWeak(void* parameters, WeakReferenceCallback callback);
Where WeakReferenceCallback is defined as a pointer-to-function taking two parameters:
typedef void (*WeakReferenceCallback)(Persistent<Object> object,
void* parameter);
These are found in the v8.h header file distributed with V8 as the public API.
You would want the function you pass to MakeWeak to clean up the Persistent<Object> object parameter that will get passed to it when it's called as a callback. The void* parameter parameter can be ignored (or the void* parameter can point to a C++ structure that holds the objects that need cleaning up):
void CleanupV8Point(Persistent<Object> object, void*)
{
// do whatever cleanup on object that you're looking for
object.destroyCppObjects();
}
Parameter<ObjectTemplate> my_obj(ObjectTemplate::New());
// when the Javascript part of my_obj is about to be collected
// we'll have V8 call CleanupV8Point(my_obj)
my_obj.MakeWeak(NULL, &CleanupV8Point);
In general, if a garbage-collected language can hold references to resources outside of the language engine (files, sockets, or in your case C++ objects), you should provide a 'close' method to release that resource ASAP, no point waiting until the GC thinks it's worthwhile to destroy your object.
it gets worse if your C++ object is memory-hungry and the garbage-collected object is just a reference: you might allocate thousands of objects, and the GC only sees a few KB's of tiny objects, not enough to trigger collection; while the C++ side is struggling with tens of megabytes of stale objects.
Do all your work in some closed scope (of object or function).
Then you can safely remove the C++ object when you went out of scope. GC doesn't check pointers for existence of pointed objects.

Categories