AngularJS和网络工作者


74

angularJS如何使用Web Worker在后台运行进程?我有什么模式可以遵循吗?

当前,我正在使用在单独的Web worker中具有模型的服务。该服务实现的方法如下:

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

在实现中,此方法将消息与数据一起发送给工作程序。这使我可以抽象出它是在单独的线程中执行的事实,并且我还可以提供一种对服务器甚至在同一线程中执行此操作的服务器进行查询的实现。

由于我是Java语言的新手,我只是在回收其他平台上的知识,因此我想知道这是否是您可以做的事情,或者我正在使用的Angular提供了一种实现方法。这也引入了我的体系结构中的更改,因为工作人员必须将更改显式地推送到控制器,然后更新其值,然后将其反映在视图中,我是否对此进行了过度设计?令人沮丧的是,Web工作者通过不允许我共享内存等来“保护”我太多,以防万一。

Answers:


98

与Web Worker的通信通过消息传递机制进行。拦截这些消息会在回叫中发生。如您所知,在AngularJS中,放置Web工​​作者的最佳位置是在服务中。解决此问题的最佳方法是使用诺言,Angular可以很好地使用诺言。

这是webworker一个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很可能可以通过web workerhttp或在此处进行处理来处理数据。

希望这是有道理的。


5
什么是doWork.js中无功工人=新员工(“doWork.js中”);
2013年

2
它是对包含Web工作人员代码的外部js文件的引用。
ganaraj

3
是的,可以在doWork.js中使用$ http这样的服务吗?
iLemming 2014年

9
如果doWork在工作者完成之前再次调用,是否会defer被覆盖?然后,第二个承诺将以第一个结果解决,而第一个承诺将永远不会解决。
拥抱

1
@hughes根据我对代码的阅读,是的,我认为你是对的。每次修改doWork(data)时,都需要修改此代码以提供新的延迟来解决。
ryanm 2015年

16

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

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

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

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

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

我发表了一篇文章,在此详细说明了这一点并创建了一个github存储库,该存储库创建了一个服务,该服务实现了此处讨论的思想。


对于>> v1.5.7的角度,请考虑添加self.Node = {prototype:[]}; 参见github.com/FredrikSandell/angular-workers/issues/15
Adavo

11

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

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

}]);

它使用Promise等待工作者返回结果。


3
托管该示例的站点不再可用。这里的档案链接web.archive.org/web/20150709233911/http://...
迈克尔·哈利利

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中测试


@ChanuSukamo这没有被调用。而且您已经错过了未在任何地方使用的pollingInterval。
IamStalker

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/

-瓦迪姆


这太棒了!!
MattE'3

@vadimk我在使用您的插件时遇到此错误!vkThread不是函数
DevLoverUmar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.