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
数据构建搜索查询,并查询Gardens以查看是否符合条件。我正在使用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
中。
如果使用作业队列实现,当将来提交一个推送到期日期的新读数时,更新现有作业或删除旧作业并创建新作业会更好吗?
答案 0 :(得分:2)
有没有办法反应这样做? 是的
这通常是通过某种作业队列来处理的吗? 是的
我最推荐的模式是定期检查“过期”事件的某种作业队列。
至于反应性 - 它在每种情况下都不能很好地发挥作用。基于计时器的依赖性失效有更高效的结构。
以下代码可用于为基于响应的计时器系统,甚至是收集支持的计时器系统供电。
如果您觉得这个答案没有达到标准,请发表评论,以便我可以对其进行改进。
// 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;
})();