Appendix. Structured Clone Algorithm

The structured clone algorithm is a mechanism that JavaScript engines use when copying objects using certain APIs. Most notably, it’s used when passing data between workers, though other APIs use it as well. With this mechanism, data is serialized and then later deserialized as an object inside of another JavaScript realm.

When objects are cloned in this manner, such as from the main thread to a worker thread or from one worker to another, modifying an object on one side will not affect the object on the other side. There are essentially two copies of the data now. The purpose of the structured clone algorithm is to provide a friendlier mechanism for developers than what JSON.stringify does, while imposing reasonable limitations.

Browsers use the structured clone algorithm when copying data between web workers. Node.js, similarly, uses it when copying data between a worker thread. Basically, when you see a .postMessage() call, data being passed in is cloned in this way. Browsers and Node.js follow the same rules but they each support additional object instances that can be copied.

As a quick rule of thumb, any data that can be cleanly represented as JSON can be safely cloned in this manner. Sticking to data represented in this manner will certainly lead to very few surprises. That said, the structured clone algorithm supports several other types of data as well.

First off, all of the primitive data types available in JavaScript, with the exception of the Symbol type, can be represented. This includes the Boolean, null, undefined, Number, BigInt, and String types.

Instances of Array, Map, and Set, which are each used for storing collections of data, can also be cloned in this manner. Even ArrayBuffer, ArrayBufferView, and Blob instances, which store binary data, can be passed along.

Instances of some more complex objects, as long as they are quite universal and well understood, can also be passed through. This includes objects created using the Boolean and String constructor, Date, and even RegExp instances1.

On the browser side, more complex and lesser-known object instances like those for File, FileList, ImageBitmap, and ImageData can be cloned.

On the Node.js side, special object instances that can be copied over include WebAssembly.Module, CryptoKey, FileHandle, Histogram, KeyObject, MessagePort, net.BlockList, net.SocketAddress, and X509Certificate. Even instances of ReadableStream, WritableStream, and TransformStream can be copied.

Another notable difference that works with the structured clone algorithm, but doesn’t work with JSON objects, is that recursive objects (those with nested properties that reference another property) can also be cloned. The algorithm is smart enough to stop serializing an object once it encounters a duplicate, nested object.

There are several “shortcomings” that may affect your implementations. First, a Function cannot be cloned in this manner. Functions can be pretty complex things. For example, they have a scope available and can access variables declared outside of them. Passing something like that between realms wouldn’t make a whole lot of sense.

Another missing feature, that will likely affect your implementations, is that DOM elements in the browser cannot be passed along. Does this mean that the work that a web worker performs can’t be displayed to the user in the DOM? Absolutely not. Instead, you’ll need to have a web worker return a value that the main JavaScript realm is then able to transform and display to the user. For example, if you were to calculate 1,000 iterations of fibonacci in a web worker, the numeric value could be returned, and the calling JavaScript code could then take that value and place it in the DOM.

Objects in JavaScript are fairly complex. Sometimes they can be created using the object literal syntax. Other times they can be created by instantiating a base class. And still, other times they can be modified, by setting property descriptors and setters and getters. When it comes to the structured clone algorithm, only the basic values of objects are retained.

Most notably, this means when you define a class of your own and pass an instance to be cloned, only the own properties of that instance will be cloned, and the resulting object will be an instance of Object. Properties defined in the prototype will not be cloned either. Even if you define class Foo {} both on the calling side and inside of the web worker, the value will still be an instance of Object. This is because there’s no real way to guarantee that both sides of the clone are dealing with the exact same Foo class.2

Certain objects will entirely refuse to cloned. For example, if you try to pass window from the main thread to a worker thread, or if you try to return self in the opposite direction, you may receive one of the following errors, depending on the browser:

Uncaught DOMException: The object could not be cloned.
DataCloneError: The object could not be cloned.

There are some inconsistencies across JavaScript engines, so it’s best to test your code in multiple browsers. For example, Chrome and Node.js supports cloning Error instances though Firefox currently does not3. The general rule of thumb is that JSON-compatible objects should never be a problem, but more complex data might be. For that reason, passing around simpler data is usually best.

1 There is a small caveat with RegExp instances. They contain a .lastIndex property, which is used when running a regular expression multiple times over the same string to know where the expression last ended. This property is not passed along.

2 There are proposals to allow serializing and deserializing class instances, such as https://github.com/littledan/serializable-objects, so this restriction might not be permanent.

3 Firefox is planning on supporting this eventually. See https://bugzilla.mozilla.org/show_bug.cgi?id=1556604

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.80.173.25