javascriptweb-workerworker

Extract a list of Transferable from a javascript object?


The postMessage() method of the Worker interface allows to pass a transferList which is an array of objects whose references should be passed (rather than the objects being copied).

myWorker.postMessage(aMessage, transferList);

Is there a safe way to recursively extract all objects that can have their references passed (according to the API, only MessagePort and ArrayBuffer), so we don't have to worry about passing all of them manually?

myWorker.postMessage(aMessage, extractTransferList(aMessage));

Is it recommended to implement this behavior?

Example:

When we consider the following code:

var objData =
{
    str: "string",
    i8: new Int8Array(200),
    obj : {
       num : 8,
       ab: new ArrayBuffer(100)
    }  
};
objWorker.postMessage(objData, [objData.i8.buffer, objData.obj.ab]);

We need to specify in the second parameter all fields of our objects that should have their references passed. My question is about building a function that does the job for us, to make sure that we don't forget some transferable objects here (that could impact performances without us knowing it).

objWorker.postMessage(objData, extractTransferList(objData));

Solution

  • My question is about building a function that does the job for us, to make sure that we don't forget some transferable objects here (that could impact performances without us knowing it).

    You could implement a function that recurses through the object graph looking for transferrable objects and builds an array of them, e.g.:

    // *Sketch*, not debugged code!!
    function getTransferrables(obj, array) {
        var key, value;
        if (!array) {
            array = [];
        }
        for (key in obj) {
            // (Add a hasOwnProperty check here if you want one)
            value = obj[key];
            if (value !== null) {
                if (typeof value === "object") {
                    if (isTransferrable(value)) {
                        array.push(value);
                    } else { // I *think* you want an else here, or should we recurse into transferrables?
                        getTransferrables(value, array);
                    }
                }
            }
        }
        return array;
    }
    var toString = Object.prototype.toString;
    function isTransferrable(obj) {
        switch (toString.call(obj)) {
            case "[object MessagePort]": // I checked these on Chrome and Firefox,
            case "[object Int8Array]":   // you'll want to verify on your target
            case "[object Int16Array]":  // browsers!
            // ...etc...
                return true;
            default:
                return false;
        }
    }
    

    Then you'd use getTransferrables like this:

    foo.postMessage(obj, getTransferrables(obj));
    

    If you find that the toString trick in isTransferrable isn't reliable cross-browser, you can use the constructor property of the object instead, e.g.:

    var ctor = obj.constructor;
    return ctor === MessagePort ||
           ctor === Int8Array ||
           ctor === Int16Array ||
           // ...
           ;
    

    You'll have to check which (if either) is more reliable, and possibly even have to mix the two to get good cross-browser coverage.

    You'll note I haven't used instanceof in isTransferrable, which would seem like it would be useful. The problem is that typed arrays are subclassable in ES2015:

    class MyNiftyArray extends Int32Array {
        // ...
    }
    

    and when you do that, instances of MyNiftyArray are instanceof the base class:

    let a = new MyNiftyArray();
    console.log(a instanceof Int32Array); // true
    

    ...but we don't know that the subclass instance is, in fact, transferrable.

    However, if you're only using this stuff with your own code, and you know you never subclass a tranferrable type, you could indeed use instanceof.

    // Relies on knowing that `obj` is not an instance of a subclass of a
    // tranferrable
    function isTransferrable(obj) {
        return obj instanceof MessagePort ||
               obj instanceof Int8Array ||
               obj instanceof Int16Array ||
               // ...
               ;
    }