Lachlan
Lachlan

Reputation: 911

How to pass functions to JavaScript Web Worker

I would like to pass a function (or functions) via a postMessage() to a web worker, because I can't refer to regular files.

To kick the web worker off, I am passing an object URL (created from a Blob) to the Worker constructor. Then I am passing a message, but so far no luck putting a function in the message.

The (JSON) message cannot contain functions directly (as stipulated here), and although importScripts is theoretically allowed, I have not had any success using it so far in Chrome or Firefox.

The body of the html file:

<div id="divText">1234</div>
<script>
    var greeter = function greet(name) {
        return "hello " + name;
    };
    function webWorkerWorker() {
        self.postMessage("started1");
        self.onmessage = function(event) {
            importScripts(event.data.content);
            self.postMessage("importScripts success");
            var result = greeter("john");
            self.postMessage(result);
        };
    }
    var functionBody = mylib.extractFunctionBody(webWorkerWorker);
    var functionBlob = mylib.createBlob([functionBody]);
    var functionUrl = mylib.createObjectURL(functionBlob);

    var functionBody2 = mylib.extractFunctionBody(greeter);
    var functionBlob2 = mylib.createBlob([greeter]);
    var functionUrl2 = mylib.createObjectURL(functionBlob2);

    var worker = new Worker(functionUrl);
    worker.onmessage = function(event) {
        document.getElementById("divText").innerHTML = event.data;
    }
    worker.postMessage({
                type: "init",
                content: functionUrl2
            });
</script>

Currently it results in setting the divText value to "importScripts success".

Am I doing something wrong? Is there another way that functions can be passed to web workers? Or is it not possible?

Upvotes: 38

Views: 30947

Answers (4)

Junaid Firdosi
Junaid Firdosi

Reputation: 127

Function objects cannot be duplicated by the structured clone algorithm, so it's not possible for us to postMessage a callback function, doing so should give you DATA_CLONE_ERR exception

Upvotes: 0

Fernando Carvajal
Fernando Carvajal

Reputation: 1945

Yes, of course it is possible, I implemented it

This is a promise that will execute the generic worker

/*
    @data.context, The context where the callback functions arguments are, ex: window
    @data.callback, ["fn_name1", "fn_name2", function (fn1, fn2) {}]
        The callback will be executed, and you can pass other functions to that cb
    @worker_url string url of generic web worker
*/
function genericWorker(worker_url, data) {
    return new Promise(function (resolve, reject) {

        if (!data.callback || !Array.isArray(data.callback))
            return reject("Invalid data")

        var callback = data.callback.pop()
        var functions = data.callback
        var context = data.context

        if (!worker_url)
            return reject("Worker_url is undefined")

        if (!callback)
            return reject("A callback was expected")

        if (functions.length>0 && !context)
            return reject("context is undefined")

        callback = fn_string(callback) //Callback to be executed
        functions = functions.map((fn_name)=> { return fn_string( context[fn_name] ) })

        var worker = new Worker(worker_url)

        worker.postMessage({ callback: callback, functions: functions })

        worker.addEventListener('error', function(error){
            return reject(error.message)
        })

        worker.addEventListener('message', function(e) {
            resolve(e.data)
            worker.terminate()

        }, false)


        //From function to string, with its name, arguments and its body
        function fn_string (fn) {
            var name = fn.name
            fn = fn.toString()

            return {
                name: name,
                args: fn.substring(fn.indexOf("(") + 1, fn.indexOf(")")),
                body: fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}"))
            }
        }

    })
}

The generic worker file worker_for_anything.js

self.addEventListener('message', function(e) {    
    var worker_functions = {} //Functions used by callback
    var args = [] //Arguments of the callback

    for (fn of e.data.functions) {
        worker_functions[fn.name] = new Function(fn.args, fn.body)
        args.push(fn.name)
    }

    var callback = new Function( e.data.callback.args, e.data.callback.body) //Callback passed and ready to be executed    
    args = args.map((fn_name) => { return worker_functions[fn_name] }) //FUnctions loaded as arguments
    var result = callback.apply(null, args) //executing callback with function arguments
    self.postMessage( result )

}, false)

Using it :)

var data = {
    context: window, //the context of the functions passed, ex: window for blockCpu
    callback: ["blockCpu", function (bla) {
        bla(7000) //blocking for 7000 ms
        return "bla" //This return is catched in the promise
    }]
}

genericWorker("/worker_for_anything.js", data)
    .then(function (result){
        console.log("result", result)

    }).catch((error)=> { console.log(error) })

//random blocking function
function blockCpu(ms) {
    var now = new Date().getTime();
    var result = 0
    while(true) {
        result += Math.random() * Math.random();
        if (new Date().getTime() > now +ms)
            return;
    }   
}

Upvotes: 1

vadimk
vadimk

Reputation: 1471

For those who are looking for more generic answer: here is a plugin, which allows you to execute any function of your javascript code in a thread.

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

Consider it as "function outsourcing". You pass any function to the plugin as an argument and get result in callback. You also can "outsource" object's methods, function with dependecies, anonymous function and lambda.

Enjoy.

--Vadim

Upvotes: 5

Lachlan
Lachlan

Reputation: 911

Turns out this method works fine, there was merely a bug in my worker:

var result = greeter("john");

should be

var result = greet("john");

which makes sense - I'm passing the greeter variable to the worker, but there's no reason for it to know the variable name of the object I'm passing.

Upvotes: 11

Related Questions