75

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.

4

5 回答 5

99

与 Web 工作者的通信是通过消息传递机制进行的。拦截这些消息发生在回调中。在 AngularJS 中,放置 web worker 的最佳位置是在服务中,正如您所指出的那样。处理这个问题的最好方法是使用 Promise,Angular 可以很好地使用它。

这是 awebworker中的一个示例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;
        }
    };

});

现在,任何访问 Hello World 服务的外部实体都不需要关心 - 的实现细节,HelloWorldService可能HelloWorldService会通过 a web worker、 overhttp或在那里进行处理。

希望这是有道理的。

于 2013-05-24T08:38:23.373 回答
17

一个非常有趣的问题!我发现网络工作者规范有点尴尬(可能有充分的理由,但仍然很尴尬)。需要将工作代码保存在单独的文件中,这使得服务的意图难以阅读,并在 Angular 应用程序代码中引入了对静态文件 URL 的依赖关系。这个问题可以通过使用 URL.createObjectUrl() 来缓解,该 URL.createObjectUrl() 可用于为 JavaScript 字符串创建 URL。这允许我们在创建 worker 的同一文件中指定 worker 代码。

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

Web Worker 规范还将工作线程和主线程上下文完全分开,以防止可能发生死锁和活锁等情况。但这也意味着如果不进行一些摆弄,您将无法访问工作人员中的角度服务。worker 缺少我们(和 angular)在浏览器中执行 JavaScript 时所期望的一些东西,例如全局变量“document”等。通过在 worker 中“模拟”这些必需的浏览器功能,我们可以让 angular 运行。

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

某些功能显然不起作用,绑定到 DOM 等。但是注入框架和例如 $http 服务可以正常工作,这可能是我们在工作人员中想要的。我们从中获得的是,我们可以在工作人员中运行标准的角度服务。因此,我们可以对 worker 中使用的服务进行单元测试,就像我们对任何其他角度依赖一样。

我发表了一篇文章,在这里详细说明了这一点,并创建了一个 github 存储库,它创建了一个服务,该服务实现了上面讨论的想法

于 2015-02-17T20:44:11.023 回答
11

我在 Angular 中找到了一个完整的网络工作者示例here

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

}]);

它使用承诺来等待工作人员返回结果。

于 2014-10-31T12:22:53.883 回答
8

带有轮询示例的 Angular Web Worker

当您在 AngularJS 中处理工作人员时,通常需要您的工作人员脚本是内联的(如果您使用一些构建工具,如 gulp/grunt),我们可以使用以下方法实现这一点。

下面的示例还显示了如何使用 worker 对服务器进行轮询:

首先让我们创建我们的工人工厂:

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

接下来从我们的控制器调用工人工厂:

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

您可以随时调用myWorker.stopWork();从您的控制器中终止工作人员!

这是在 IE11+ 和 FF 和 Chrome 中测试的

于 2016-05-11T08:06:00.890 回答
2

您还可以查看角度插件https://github.com/vkiryukhin/ng-vkthread

它允许您在单独的线程中执行函数。基本用法:

/* 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 
    }
);

示例和 API 文档:http ://www.eslinstructor.net/ng-vkthread/demo/

——瓦迪姆

于 2016-06-30T02:53:34.193 回答