【问题标题】:AngularJS and web workersAngularJS 和网络工作者
【发布时间】:2013-05-18 19:30:25
【问题描述】:

angularJS 如何使用 web worker 在后台运行进程?这样做有什么我应该遵循的模式吗?

目前,我正在使用一个在单独的网络工作者中具有模型的服务。该服务实现的方法如下:

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

在实现中,此方法向工作人员发送带有数据的消息。这使我可以抽象出它在单独的线程中执行的事实,并且我还可以提供一个查询服务器的实现,甚至可以提供一个在同一线程中执行此操作的服务器。

由于我是 javascript 新手,而且我只是在回收我从其他平台获得的知识,我想知道这是否是你会做的事情,或者也许我正在使用的 Angular 提供了一种这样做的方式。这也引入了我的体系结构的变化,因为工作人员必须明确地将更改推送到控制器,然后更新其值,然后这反映在视图中,我是否过度设计了这个?网络工作者通过不允许我共享内存等来“保护”我免于搞砸,这有点令人沮丧。

【问题讨论】:

    标签: javascript angularjs web-worker


    【解决方案1】:

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

    这是servicewebworker 的示例

    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 处理数据或直接在那里进行处理。

    希望这是有道理的。

    【讨论】:

    • var worker = new Worker('doWork.js'); 中的 doWork.js 是什么?
    • 它是对包含 web-worker 代码的外部 js 文件的引用。
    • 是的,可以在doWork.js中使用像$http这样的服务吗?
    • 如果在worker完成之前再次调用doWorkdefer不会被覆盖吗?然后第二个 promise 会用第一个结果解析,而第一个 promise 永远不会解析。
    • @hughes 根据我对代码的阅读,是的,我认为您是对的。每次调用 doWork(data) 时,都需要修改此代码以提供新的 defer 来解决。
    【解决方案2】:

    一个非常有趣的问题!我发现网络工作者规范有点尴尬(可能有充分的理由,但仍然很尴尬)。需要将工作代码保存在单独的文件中,这使得服务的意图难以阅读,并在 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 中使用的服务进行单元测试。

    我发了一篇文章详细说明了这个here,并创建了一个github repo,它创建了一个服务来实现上面讨论的想法here

    【讨论】:

    【解决方案3】:

    我在 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;
        });
    
    }]);
    

    它使用promise来等待worker返回结果。

    【讨论】:

    【解决方案4】:

    带有轮询示例的 Angular Web Worker

    当您在 AngularJS 中处理 worker 时,它通常要求您的 worker 脚本是内联的(如果您使用一些构建工具,例如 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。
    【解决方案5】:

    你也可以看看angular插件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/

    --瓦迪姆

    【讨论】:

    • 这太棒了!!
    • @vadimk 我在使用你的插件时遇到了这个错误! vkThread 不是函数
    猜你喜欢
    • 2021-11-09
    • 2010-12-24
    • 1970-01-01
    • 2016-06-08
    • 2014-05-29
    • 2016-02-21
    • 2015-03-16
    • 2013-04-25
    相关资源
    最近更新 更多