流星中的状态检查延迟

Delayed state checking in Meteor

本文关键字:检查 延迟 状态 流星      更新时间:2023-09-26

延迟状态检查

tl;dr - 当操作在指定的持续时间内未发生时,有哪些好的反应设计模式?


问题设置

我正在制作一个类似Farmville的应用程序。在此应用程序中,用户拥有他们照顾的花园。每个花园都有几个变量被跟踪 - 湿度,温度,pH值,硝酸盐等。该应用程序将指导您照顾花园,为您创建任务。它会根据您提交的读数通知您这些变量中的任何一个是太低还是太高。此外,如果您在指定的时间内没有读取变量,它会提醒您读取变量。

花园数据看起来像

// garden object
{
  name: "Home garden",
  variables: {
    nitrate: {
      currentValue: 1.7,
      lastMeasuredAt: // some Date
    },
    nitrite: {
      currentValue: 0.5,
      lastMeasuredAt: // some Date
    }
  }
}

因此,假设有一个名为"添加肥料"的任务。此任务的触发因素是硝酸盐低于 2.5ppm 的情况。假设任务数据看起来像

// task object
{
  name: "Add fertilizer",
  instructions: "Open bag, dump on garden",
  condition: {
    variable: "nitrate",
    operator: "$lt",
    threshold: 2.5
  }
}

我使用condition数据构建一个搜索查询,并查询花园以查看是否有任何符合该条件的内容。我使用的是MongoDB,所以可以使用普通的Javascript对象和Meteor来形成该查询,所以我有一个实时更新的游标。

query = { variables.nitrate.currentValue: { $lt : 2.5 }};
Gardens.find(query).observe({
  added: function( garden ) {
    // add the task to the garden's task list
  },
  removed: function( garden ) {
    // remove the task from the garden's task list
  }
});

问题所在

好消息是,这种模式适用于我描述的任务。但是,当任务基于经过的时间持续时间时呢?

// time-based task object
{
  name: "Take a nitrate reading",
  instructions: "Insert the nitrate probe",
  condition: {
    variable: "nitrate",
    operator: "$lt",
    interval: 2880 // 2 days in minutes
  }
}

我看到这是一个时间任务,因为它有一个interval而不是一个threshold并进行查询......

// using moment.js
expiration = moment().subtract(interval, 'minutes').toDate();
// find when lastMeasuredAt + interval < Now
query = { variables.nitrate.lastMeasuredAt: { $gt: expiration }};
Gardens.find(query).observe({
  added: function( garden ) {
    // add the task to the garden's task list
  },
  removed: function( garden ) {
    // remove the task from the garden's task list
  }
});

但这需要在将来的某个时候进行检查,而不是现在。这就引出了我问题的症结所在。检查何时过期的好方法是什么?

问题

有没有办法被动地做到这一点?

Meteor 的Tracker仅支持在客户端上运行,但peerlibrary:server-autorun确实在服务器上启用了它。我可以将 Date 设置为反应式数据源,每隔几分钟或其他时间更新一次,并将其包装在Tracker.autorun中。

这通常由某种作业队列处理吗?

如果使用作业队列实现,当将来提交新的读数将到期日期推回时,是更新现有作业更好还是删除旧作业并创建新作业更好?

有什么方法可以被动地做到这一点吗?是的

这通常由某种作业队列处理吗?是的

我最推荐的模式是定期检查某种作业队列中的"过期"事件。

至于反应性 - 它并非在所有情况下都能很好地工作。有更有效的结构,基于计时器的依赖项失效。

下面的代码,可用于为基于反应的计时器系统供电,甚至是集合支持的计时器系统。

如果你觉得这个答案不太到位,请发表评论,这样我就可以完善它。

// executes it's tasks regularly.
// up to the tasks system to implement "enqueue" & "execute"
ScheduledExecutor = (function() {
  function ScheduledExecutor(tasks, checkInterval) {
    this.tasks = tasks;
    this.checkInterval = checkInterval;
    this.running = false;
    this.timer = null;
    this.tick = _.bind(this.tick, this);
  }
  _.extend(ScheduledExecutor.prototype, {
    enqueue: function(task){
      this.tasks.enqueue(task);
    },
    tick: function(){
      this.tasks.execute();
      if (this.running) {
        this.timer = Meteor.setTimeout(this, this.checkInterval);
      }
    },
    start: function(){
      if (!this.running){
        this.running = true;
        this.tick();
      }
    },
    stop: function(){
      this.running = false;
      Meteor.clearTimeout(this.timer);
      this.timer = null;
    }
  });
  return ScheduledExecutor;
})();
// example of "reactive" task list.
//  finds due dependencies, and invalidates them
ReactiveTasks = (function(){
  function ReactiveTasks(){
    this.taskList = [];
  }
  _.extend(ReactiveTasks.prototype, {
    execute: function(){
      var now = Date.now();
      _.findWhere(this.taskList, function(task){
        return task.due <= now;
      }).forEach(function(task){
        task.dep.changed()
      });
      this.taskList = _.filter(this.taskList, function(task){
        return task.due > now;
      });
    },
    enqueue: function(due){
      var dep = new Tracker.Dependency;
      dep.depend();
      this.taskList.push({
        due: due,
        dep: dep
      });
    }
  });
  return ReactiveTasks;
})();
// executes tasks backed by a collection
//  finds "due" tasks, then calls the "executor"
//  the executor should interpret the task, and 
/// call the correct library function
CollectionTasks = (function(){
  function CollectionTasks(collection, executor){
    this.collection = collection;
    this.executor = executor;
  }
  _.extend(CollectionTasks.prototype, {
    execute: function(){
      var self = this, 
        now = Date.now();
      self.collection.find({
        due: {
          $lte: now
        }
      }).forEach(function(task){
        self.collection.remove({_id: task._id});
        self.executor.execute(task);
      });
    },
    enqueue: function(task){
      this.collection.insert(task);
    }
  });
  return CollectionTasks;
})();