定时任务是指不需要通过手动调用,其执行函数或代码会在指定的时间自动运行。
在JavaScript中提供了setTimeout和setInterval。clearInterval和clearTimeout两对函数进行定时任务的处理。
其中setTimeout只执行一次,而setInterval按时间间隔反复执行。
clearInterval和clearTimeout函数则是分别用来取消setTimeout和setInterval的设定的定时任务。
如果通过setTimeout的定时任务还没有启动执行,那么只要传入该定时任务的引用到clearTimeout中就可以取消该任务。clearInterval也一样,用来取消setInterval设定的定时任务,它们都不能中断正在执行中的函数。
有状态定时任务管理
既然我们能通过来setInterval实现连续间隔地执行指定任务(即函数)。但是任务执行时有先后的关联,即状态性。比如需要根据上一次的执行看看其结果是否满足,如果满足就不去继续执行,而中断该任务。
而setInterval做不到,它到了一定的间隔时间就会执行,它不会去关注上一次执行是否结束,它是另外开启新的线程并发执行。那么如何去实现任务的状态管理呢。有二种方法,一是采用setTimeout模拟,二是采用setInterval实现。
Ext.util.DelayedTask
Ext.util.DelayedTask类从名字上就可以看出它是推迟任务管理。它用来实现隔指定时间长度之后执行任务,与PeriodicalExecuter不同,它不是针对于同个任务,而可能是多个任务,上一次任务如果还没有执行,它不是等待,而是取消转而执行当前任务。
比较典型的应用是ExtJS的事件配置中的buffer配置项,它用来指定延迟一段时间执行事件监听函数,如果在这个延迟时间之内,又有另外触发要执行其监听函数,上一次没有执行的任务取消,而又延迟buffer提定的时间去执行新任务。
在Ext.util.Event中有两个这样的函数用来处理buffer和delay配置参数:
var createBuffered = function(h, o, scope){
var task = new Ext.util.DelayedTask();
return function()...{
task.delay(o.buffer,h,scope,
Array.prototype.slice.call(arguments, 0));};
};
var createDelayed = function(h, o, scope)...{
return function()...{
var args = Array.prototype.slice.call(arguments, 0);
setTimeout(function()...{h.apply(scope, args);}, o.delay || 10);};
};
它们肯定是有区别的,createDelayed是用来进行延迟指定时间后去执行任务。它采用setTimeout完成,这个对于每个任务都是一定会执行的。而createBuffered不一同,对于同一个事件监听,它定义了一个任务DelayedTask,如果在其buffer时间里多次触发,那么它只执行最后一次触发的任务。
我们来看一下DelayedTask的delay函数:
this.delay = function(delay, newFn, newScope, newArgs){
if(id && delay != d)...{ this.cancel(); } ①
d = delay;
t = new Date().getTime();
fn = newFn || fn;
scope = newScope || scope;
args = newArgs || args; ②
if(!id)...{ id = setInterval(call, d);} }; ③
当构建一个DelayedTask实例,我们就可以多次来调用其delay方法来延迟其任务的执行。上面的函数有四个参数,其中命名有new前缀的都是可以省略采用实例中的任务(即通过构建函数参数传入。在①处,它用来判断该实例是否已经有任务正在执行,如果正在执行的任务的时间间隔和当前参数中指定的时间间隔不一样,就通过cancel取消正在执行中任务,取消就是通过clearInterval来取消,并把id设为null。
在①②之间的代码就是把参数中值扩大到整个实例范围让其实例所有函数都能访问。③处是每隔指定的时间来执行当前实例的call函数。这个call函数当然是包裹函数,用来包裹定位任务,即参数中的回调函数。这也就是为什么要把参数中传入值扩大范围。
var call = function(){
var now = new Date().getTime();
if(now - t >= d)...{
clearInterval(id);
id = null;
fn.apply(scope, args || []); }
};
在call函数中,它在执行任务之前就会取消下一次定时任务,并把id设为null。可以看出该定位尽管是通过setInterval,它至多只运行一次。这里可以看作是采用setInterval模拟setTimeout。
①②③④⑤⑥⑦⑧⑨⑩
3.9.2定时任务管理器
上面的定时任务都是单个任务,如果需要对一组定时任务进行操作怎么办?ExtJS提供了Ext.TaskMgr实例用来进行默认组的定时任务管理。它是通过Ext.util.TaskRunner类完成。
Ext.util.TaskRunner = function(interval){
interval = interval || 10;
var tasks = [], removeQueue = [];
var id = 0, running = false;
var stopThread = function()...{.. .. ..};
var startThread = function()...{.. .. ..};
var removeTask = function(t)...{.. .. .. };
var runTasks = function()...{ .. .. .. };
this.start = function(task)...{.. .. .. };
this.stop = function(task)...{.. .. .. };
this.stopAll = function()...{.. .. ..};
TaskRunner主要的作用是对一组定时任务进行统一地运行管理,我们可以通过其start、stop函数来运行或停止指定的任务。另外还可以通过stopAll来停止所有正在运行的任务。其实TaskRunner和第九章中的动画实例管理器很相似。
如果我们需要启动一个定时任务,我们要通过其start函数,它有task参数是一个配置对象,用来指定执行的相关配置,其中run是要执行的任务函数,interval是指定它隔多长时间执行一次,但是不能小于整个管理器设定的interval,其默认是10ms。duration是用来指定任务的执行总的时间,如果没有指定时间,也可以通过指定repeat来指定次数。scope是run函数的作用域,args是run函数的参数。我们看一下start: this.start = function(task){
tasks.push(task);
task.taskStartTime = new Date().getTime();
task.taskRunTime = 0;
task.taskRunCount = 0;
startThread();
return task; };
这里只要把task存到任务集合中去,再初始化一些值,之后通过startThread来启动一个新的线程执行任务。而它是通过setInterval(runTasks, interval);来执行runTasks函数,如果经运行了,就不会再次启动setInterval。
var runTasks = function(){
if(removeQueue.length > 0)...{ ①
for(var i = 0, len = removeQueue.length; i < len; i++)...{
tasks.remove(removeQueue[i]); }
removeQueue = [];
if(tasks.length < 1)...{ stopThread();return; }}
var now = new Date().getTime();
for(var i = 0, len = tasks.length; i < len; ++i)...{ ②
var t = tasks[i];
var itime = now - t.taskRunTime;
if(t.interval <= itime)...{ ③
var rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
t.taskRunTime = now;
if(rt===false||t.taskRunCount===t.repeat)...{removeTask(t);return;}
}
if(t.duration&&t.duration<=now-t.taskStartTime))...{removeTask(t);}④
}};
该函数分成两部分,①处是处理删除任务队列中的任务,②是处理运行任务队列中的任务。通过①处可以看出removeTask并不是从tasks中除去,而把其引用加到removeQueue中,这样能避免影响正在运行中的任务。它的删除是在下一次运行中。
②处是对每个任务都进行执行,这个执行是根据其配置项的参数来进行的,③是根据时间或其重复次数来判断当前任务是否执行。如果超过指定的时间就运行④处代码,把它移到removeTask中去。
该类在Form中有应用。