AngularJS中的生产者 - 消费者队列

时间:2015-11-04 18:50:12

标签: javascript angularjs producer-consumer

几年前我就知道python和数据库了。

但我希望提高我有限的JavaScript知识。对于我的玩具项目,我想在Web浏览器中使用异步队列并使用AngularJS。

在python中有一个名为multiprocessing.Queue的好类,我过去曾使用它。

现在我搜索这样的东西,但是在AngularJS中

async-queue-js

  • 第1步:队列中拉出工作项(粉色圆圈)。只是一个视图json bytes。

  • 第2步:用户处理数据。

  • 第3步:出队关心将结果发送到服务器。

为什么这个"复杂"建立?因为我希望应用程序尽可能地响应。队列中应该预先加载一些数据,出队列应该处理响应通信。

另一个好处是,通过此设置,应用程序可以处理服务器或网络中断几分钟。

AngularJS的双向数据绑定立即更新用户编辑的数据并不适合我的问题。或者我错过了什么。我是AngularJS的新手。

图片中的粉色圆圈代表JSON数据结构。我想通过一个请求将每个请求推送到浏览器。

示例:

用户看到一个问题,然后他需要填写三个字段。例如:

  • 回答:输入文字
  • 喜欢这个问题:整数来自1..5
  • 难度:整数从1..5

数据应在使用后按下"提交"后放入队列。他应该立即得到下一个问题。

问题:

是否已为AngularJS提供生产者 - 消费者队列?如果没有,如何实施呢?

更新

从客户端发送数据可以使用普通的AJAX实现。预取数据的队列是更复杂的部分。虽然两者都可以使用相同的实现。客户端以超低延迟获取新数据非常重要。队列中每次最多应填充5个项目,以避免客户端等待数据。

在我的情况下,如果浏览器关闭并且队列中的项目丢失则无关紧要。填充队列在服务器部分是只读的。

我没有修复AngularJS。如果有充分的理由,我很乐意改变框架。

保留浏览器重新加载之间的队列可以使用localStorage(html5)

完成

4 个答案:

答案 0 :(得分:3)

重新思考,你真的需要在你的前端生产者 - 消费者吗? 在您的示例中,我认为在这种情况下,简单的pub-sub$Q就足够了。

示例:

在用户提交问题的questionHandlerService中,在您的示例question-submitted中订阅订阅questionService的订阅者服务,只需发布​​事件question-submitted,数据,火灾和遗忘。您无需等待questionHandlerService的响应。

请记住,javascript中只有一个主线程,如果你的方法会阻止ui,比如loop through 1000 items in the array, synchronous process them,如果把它放在另一个“队列”中就没有帮助,因为它除非你在网络工作者中执行它,否则必须在执行时阻止ui。如果用户刷新浏览器会怎样?您的未处理请求刚刚丢失。

触发 XHR 调用不会阻止用户界面,在前端实现producer-consumer没有意义,你只需验证输入就可以激活 XHR < / strong>,让后端处理繁重的工作,在后端控制器中,您可以使用队列立即保存请求和响应。并从任何线程或其他进程处理队列。

答案 1 :(得分:2)

  

Working Plunker - 后端服务仅用于模拟休息接口;我修正了一些bug,例如错误限制。所以,假设Plunker是最后一个版本......

我还没有足够的时间来改善以下内容,但是,您可以将其视为起点......

顺便说一句,我认为你需要的可能是:

  1. 包装$ http。
  2. 的服务
  3. 我们将在需要注册任务时使用的方法推送
  4. 递归私有方法 _each ,逐步减少已注册的队列。
  5. ......您认为重要的其他内容(getCurrentTask,removeTask,ecc。)。
  6. 用法:

    &#13;
    &#13;
    angular
      .module('myApp', ['Queue'])
      .controller('MyAppCtrl', function($httpQueue, $scope) { 
        var vm = $scope;
        
        // using a route.resolve could be better!
        $httpQueue
          .pull()
          .then(function(tasks) { vm.tasks = tasks;  })
          .catch(function() { vm.tasks = [{ name: '', description: '' }]; })
        ;
      
        vm.onTaskEdited = function(event, task, form) {
          event.preventDefault();
          if(form.$invalid || form.$pristine ) { return; }
          
          
          return $httpQueue.push(task);
          
        };
      })
    ;
    &#13;
    <article ng-app="myApp">
      <div ng-controller="MyAppCtrl">
        
        
        <form ng-repeat="task in tasks" name="taskForm" ng-submit="onTaskEdited($event, task, taskForm)">
          <input ng-model="task.name" placeholder="Task Name" />
          <textarea ng-model="task.description"></textarea>
        </form>
        
        
      </div>
    </article>
    &#13;
    &#13;
    &#13;

    模块定义

    &#13;
    &#13;
    (function(window, angular, APP) {
      'use strict';
    
      function $httpQueueFactory($q, $http) {
        var self = this;
    
        var api = '/api/v1/tasks';
    
        self.queue = [];
        var processing = false;
    
    
        //Assume it as a private method, never call it directly
        self._each = function() {
          var configs = { cache: false };
    			
    			
          return self
            .isQueueEmpty()
            .then(function(count) {
              processing = false;
              return count;
            })
            .catch(function() {
              if(processing) {
                return;
              }
            
              processing = true;
              var payload = self.queue.shift();
         
              var route = api;
              var task = 'post';
              if(payload.id) {
                task = 'put';
                route = api + '/' + payload.id;
              }
            
              return $http
                [task](route, payload, configs)
                .catch(function(error) {
                  console.error('$httpQueue._each:error', error, payload);
                  //because of the error we re-append this task to the queue;
                  return self.push(payload);
                })
                .finally(function() {
                  processing = false;
                  return self._each();
                })
              ;
            })
          ;
        };
    
    
        self.isQueueEmpty = function() {
          var length = self.queue.length;
          var task = length > 0 ? 'reject' : 'when';
          
          return $q[task](length);
        };
    
        self.push = function(data) {
          self.queue.push(data);
          self._each();
    
          return self;
        };
    
        self.pull = function(params) {
          var configs = { cache: false };
          
          configs.params = angular.extend({}, params || {});
    
          return $http
            .get(api, configs)
            .then(function(result) {
              console.info('$httpQueue.pull:success', result);
    
              return result.data;
            })
            .catch(function(error) {
              console.error('$httpQueue.pull:error', error);
            
              return $q.reject(error);
            })
          ;
        };
      }
    
    
    
      APP
        .service('$httpQueue', ['$q', '$http', $httpQueueFactory])
      ;	
    
    })(window, window.angular, window.angular.module('Queue', []));
    &#13;
    &#13;
    &#13;

    处理DataLayer更改

    处理数据层的更改(你在队列中调用的)是一项更困难的任务,因为我们需要与当前的拉动保持同步最后一次拉 ...

    顺便说一下,如果您正在寻找具有实时通知的系统,您可能应该查看套接字层 ...我建议 Socket.io < / strong>因为是经过良好测试,行业认可的解决方案。

    如果您无法实现套接字层,则另一个解决方案可能是长轮询实施,详细说明here

    为简单起见,在这篇文章中,我们将实现一个更新当前任务列表的简单间隔... 所以,前面的例子就是:

    &#13;
    &#13;
    angular
      .module('myApp', ['Queue'])
      .controller('MyAppCtrl', function($httpQueue, $scope, $interval) { 
        var vm = $scope;
        var 
          pollingCount = 0, // infinite polling
          pollingDelay = 1000
        ;
        
        // using a route.resolve could be better!
        $httpQueue
          .pull()
          .then(function(tasks) { vm.tasks = tasks;  })
          .catch(function() { vm.tasks = [{ name: '', description: '' }]; })
          .finally(function() { return $interval(vm.updateViewModel.bind(vm), pollingDelay, pollingCount, true); })
        ;
      
        var isLastPullFinished = false;
        vm.updateViewModel = function() {
          if(!isLastPullFinished) { return; }
          
          return $http
            .pull()
            .then(function(tasks) {
              for(var i = 0, len = tasks.length; i < len; i++) {
                
                for(var j = 0, jLen = vm.tasks.length; j < jLen; j++) {
                  if(tasks[i].id !== vm.tasks[j].id) { continue; }
                  
                  // todo: manage recursively merging, in angular 1.3+ there is a
                  // merge method https://docs.angularjs.org/api/ng/function/angular.merge
                  // todo: control if the task model is $dirty (if the user is editing it)
                  angular.extend(vm.tasks[j], tasks[i]);
                }
                
              };
              
              return vm.tasks;
            })
            .finally(function() {
              isLastPullfinished = true;
            })
          ;
        };
      
        
        
        vm.onTaskEdited = function(event, task, form) {
          event.preventDefault();
          if(form.$invalid || form.$pristine ) { return; }
          
          
          return $httpQueue.push(task);
          
        };
      })
    ;
    &#13;
    &#13;
    &#13;

    希望它有所帮助; 我没有对它进行测试,因此可能存在一些错误!

答案 2 :(得分:1)

我会查看承诺,因为它们提供了您似乎需要的异步功能,可能会在初始时提取所有或部分问题。如果您确实需要离线查看服务工作者,尽管他们仅限于较新的浏览器

如果您无法使用服务工作者,您可以创建一个服务(或工厂)来保存您的所有问题或稍微提前阅读。您可以使用$ http服务尝试并提取更多答案,如果您无法使用所拥有的内容,直到再次出现网络连接。

通过尝试在$ interval的循环中获取新答案(并发布答案),可以使用$ http服务来检查网络。

答案 3 :(得分:1)

申请结构: 2个数组,一个用另一个问题答案。

应用程序启动: 两个$ interval对象,一个从服务器获得前10个问题。如果问题缓冲区长度是&lt; 10它会将新问题推入阵列。

另一个函数检查答案数组,如果通信可用,则将结果发送给服务器。

当用户检查问题时,它会弹出第一个数组并推入答案数组。

这就是全部..角度2方式数据绑定使用应用程序中的函数调用显示最老的问题,如giveMeTheLatestQuestion ...

希望它有所帮助!