AngularJS and web workers

JavascriptAngularjsWeb Worker

Javascript Problem Overview


How can angularJS use web workers to run processes in the background? Is there any pattern I should follow on doing this?

Currently, I am using a service that has the model in a separate web worker. This service implements methods like:

ClientsFacade.calculateDebt(client1); //Just an example..

In the implementation, this method sends a message to the worker with the data. This allows me to abstract the fact that it is being performed in a separate thread and I could also provide an implementation that queries against a server or even one that does this action in the same thread.

Since I'm new to javascript and I'm just recycling knowledge I have from other platforms I wonder if this is something you would do or perhaps Angular which is what I am using, offers a sort of way of doing this. Also this introduces a change in my architecture since the worker must explicitly push changes to the controller, which then updates its values and then this is reflected in the view, am I over engineering this? It's a bit frustrating that web workers "protect" me so much from screwing up by not allowing me to share memory etc.

Javascript Solutions


Solution 1 - Javascript

Communication with Web workers happens through a messaging mechanism. Intercepting these messages happens in a call back. In AngularJS, the best location to put a web worker is in a service as you duly noted. The best way to deal with this is to use promises, which Angular works amazingly with.

Here is an example of a webworker in a service

var app = angular.module("myApp",[]);

app.factory("HelloWorldService",['$q',function($q){

    var worker = new Worker('doWork.js');
    var defer = $q.defer();
    worker.addEventListener('message', function(e) {
      console.log('Worker said: ', e.data);
      defer.resolve(e.data);
    }, false);

    return {
        doWork : function(myData){
            defer = $q.defer();
            worker.postMessage(myData); // Send data to our worker. 
            return defer.promise;
        }
    };

});

Now whatever external entity that accesses Hello World service need not care about the implementation details of HelloWorldService - HelloWorldService could probably process the data over a web worker, over http or do the processing right there.

Hope this makes sense.

Solution 2 - Javascript

A very interesting question! I find the web worker specification a bit awkward (probably for good reasons, but still awkward). The need to keep the worker code in a separate file makes the intention of a service hard to read and introduces dependencies to static file URLs in your angular application code. This problem can be mitigated by using the URL.createObjectUrl() which can be used to create a URL for a JavaScript string. This allows us to specify the worker code in the same file which creates the worker.

var blobURL = URL.createObjectURL(new Blob([
    "var i = 0;//web worker body"
], { type: 'application/javascript' }));
var worker = new Worker(blobURL);

The web worker spec also keeps the worker and main thread contexts completely separate to prevent situations were deadlocks and livelocks etc can occur. But it also means you won't have access to your angular services in the worker without some fiddling. The worker lack some of the things we(and angular) expect when executing JavaScript in the browser, like the global variable "document" etc. By "mocking" these required browser features in the worker we can get angular to run.

var window = self;
self.history = {};
var document = {
    readyState: 'complete',
    cookie: '',
    querySelector: function () {},
    createElement: function () {
        return {
            pathname: '',
            setAttribute: function () {}
        };
    }
};

Some features obviously won't work, bindings to the DOM etc. But the injection framework and for example the $http service will work just fine, which is probably what we want in a worker. What we gain by this is that we can run standard angular services in a worker. We can therefore unit test the services used in the worker just as we would with any other angular dependency.

I made a post which elaborates a bit more about this here and created a github repo which creates a service which implements the ideas discussed above here

Solution 3 - Javascript

I found a fully working example of web workers in Angular [here](http://www.codingterminal.com/articles/092014/1/XMLHttpRequest%20using%20AngularJS%20Promises%20and%20HTML5%20Web%20Workers.php "Angular JS and Web Workers")

webworker.controller('webWorkerCtrl', ['$scope', '$q', function($scope, $q) {

    $scope.workerReplyUI;
    $scope.callWebWorker = function() {
        var worker = new Worker('worker.js');
        var defer = $q.defer();
        worker.onmessage = function(e) {
            defer.resolve(e.data);
            worker.terminate();
        };

        worker.postMessage("http://jsonplaceholder.typicode.com/users");
        return defer.promise;
    }

    $scope.callWebWorker().then(function(workerReply) {
        $scope.workerReplyUI = workerReply;
    });

}]);

It uses promises to wait for the worker to return the result.

Solution 4 - Javascript

> Angular Web Worker with polling example

When you are dealing with the workers in AngularJS its often required that your worker script to be inline(incase you are using some build tools like gulp/grunt) and we can achieve this using the following approach.

Example below also shows how polling can be done to server using workers:

First lets create our worker factory:

    module.factory("myWorker", function($q) {
    var worker = undefined;
    return {
        startWork: function(postData) {
            var defer = $q.defer();
            if (worker) {
                worker.terminate();
            }

            // function to be your worker
            function workerFunction() {
                var self = this;
                self.onmessage = function(event) {
                    var timeoutPromise = undefined;
                    var dataUrl = event.data.dataUrl;
                    var pollingInterval = event.data.pollingInterval;
                    if (dataUrl) {
                        if (timeoutPromise) {
                            setTimeout.cancel(timeoutPromise); // cancelling previous promises
                        }

                        console.log('Notifications - Data URL: ' + dataUrl);
                        //get Notification count
                        var delay = 5000; // poller 5sec delay
                        (function pollerFunc() {
                            timeoutPromise = setTimeout(function() {
                                var xmlhttp = new XMLHttpRequest();
                                xmlhttp.onreadystatechange = function() {
                                    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                                        var response = JSON.parse(xmlhttp.responseText);
                                        self.postMessage(response.id);
                                        pollerFunc();
                                    }
                                };
                                xmlhttp.open('GET', dataUrl, true);
                                xmlhttp.send();
                            }, delay);
                        })();
                    }
                }
            }
            // end worker function

            var dataObj = '(' + workerFunction + ')();'; // here is the trick to convert the above fucntion to string
            var blob = new Blob([dataObj.replace('"use strict";', '')]); // firefox adds user strict to any function which was blocking might block worker execution so knock it off

            var blobURL = (window.URL ? URL : webkitURL).createObjectURL(blob, {
                type: 'application/javascript; charset=utf-8'
            });

            worker = new Worker(blobURL);
            worker.onmessage = function(e) {
                console.log('Worker said: ', e.data);
                defer.notify(e.data);
            };
            worker.postMessage(postData); // Send data to our worker.
            return defer.promise;
        },
        stopWork: function() {
            if (worker) {
                worker.terminate();
            }
        }
    }
});

Next from our controller call the worker factory:

var inputToWorker = {
    dataUrl: "http://jsonplaceholder.typicode.com/posts/1", // url to poll
    pollingInterval: 5 // interval
};

myWorker.startWork(inputToWorker).then(function(response) {
    // complete
}, function(error) {
    // error
}, function(response) {
    // notify (here you receive intermittent responses from worker)
    console.log("Notification worker RESPONSE: " + response);
});

You can call myWorker.stopWork(); any time to terminate the worker from your controller!

This is tested in IE11+ and FF and Chrome

Solution 5 - Javascript

you can also take a look at angular plugin https://github.com/vkiryukhin/ng-vkthread

which allows you to execute a function in a separate thread. basic usage:

/* function to execute in a thread */
function foo(n, m){ 
    return n + m;
}

/* create an object, which you pass to vkThread as an argument*/
var param = {
      fn: foo      // <-- function to execute
      args: [1, 2] // <-- arguments for this function
    };

/* run thread */
vkThread.exec(param).then(
   function (data) {
       console.log(data);  // <-- thread returns 3 
    }
);

Examples and API doc: http://www.eslinstructor.net/ng-vkthread/demo/

--Vadim

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionarg20View Question on Stackoverflow
Solution 1 - JavascriptganarajView Answer on Stackoverflow
Solution 2 - JavascriptliketView Answer on Stackoverflow
Solution 3 - JavascriptOctaneView Answer on Stackoverflow
Solution 4 - JavascriptChanakya VadlaView Answer on Stackoverflow
Solution 5 - JavascriptvadimkView Answer on Stackoverflow