65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript Web 的任务计划程序服务

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2015 年 8 月 14 日

CPOL

5分钟阅读

viewsIcon

25088

downloadIcon

506

任务计划程序 JavaScript 库

引言

任务调度器是操作系统中一个有用且重要的功能。Windows 下是 Task Scheduler,Linux 下是 Crontab,MAC 下是 Cron。乍一看,这项工作很简单,但它对于启用您在计算机上自动执行例行任务至关重要。它通过监控您选择的任何条件来启动任务(称为触发器),然后在满足条件时执行任务。可以调度的最常见任务包括启动应用程序、发送电子邮件、显示消息或备份敏感用户数据。

任务可以以不同的方式调度,例如

  • 当发生特定系统事件时
  • 在特定时间
  • 在特定时间的每日计划中
  • 在特定时间的每周计划中
  • 在特定时间的每月计划中
  • 在特定时间的每月星期几计划中
  • 当计算机进入空闲状态时

我将介绍一个基于 JavaScript 的为 Web 应用程序提供任务调度的库。现在,大多数 Web 应用程序都有厚客户端,大部分功能都在客户端运行,仅通过请求服务器来更新数据。这些客户端处理用户输入和管理后台任务的工作量很大,因此在功能上更接近操作系统。其中任务调度器扮演着重要角色。

在项目初期,它只是一个简单的复制系统的一部分。主要目标是创建和编辑现有的 Crontab 调度模式。但正如通常发生的那样,随着新功能的添加,项目不断壮大,现在它已经成为一个完整的任务调度器。

目录

学习要点

数据结构由两部分组成:调度器和任务管理。调度器系统负责解析、创建、编辑基于 Crontab 格式的字符串,它还根据模式计算下一个和上一个调度日期,并准备人类可读的发生字符串。

作为一项附加功能,它可以计算用户指定的任何时间点之前的所有下一个和上一个调度日期。

调度器数据结构由调度器类和六个辅助类组成:minuteContainerhourContainerdayContainermonthContainerweekContainerbaseContainer,它们封装了通用功能并作为它们的超类。

调度器在输入以下格式的 Crontab 基础上操作 string

// 

.------------------- minute (0 - 59)                     
|  .---------------- hour (0 - 23) 
|  |  .------------- day of month (1 - 31) 
|  |  |  .---------- month (1 - 12) 
|  |  |  |  .------- day of week (0 - 7) (Sunday=0,7) 
|  |  |  |  | 

*  *  *  *  *
//

根据模式,分钟、小时、日期、月份和星期几容器将创建并存储值。容器是基于输入数据形成的数组,并包含操作它们的方法。数组的形成方式使得数组的值及其分布应对应于输入模式,并且数组的值可以通过索引轻松检索。例如,如果我们有一个模式“*/5 2/3 * * *”,那么分钟容器将根据第一个参数 */5 等于 [0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15, 15, …, 55, 55, 55, 55, 55]。这可以避免在计算下一个发生时进行循环。为了获得下一个发生,我们只需要通过当前日期的分钟的索引从数组中检索值。例如,对于日期:08/12/2015 (15:03),当前分钟为 03,在索引 03 的容器中我们将得到值 5,所以下一个发生将在 08/12/2015 (17:05),因为每 3 小时发生一次,从 2 点开始,每 5 分钟发生一次,从 0 点开始。

可能会出现这样的问题:为什么它操作分钟、小时等的哈希值数组,而不是直接操作毫秒。两种方式都是可能的。也许,当前解决方案首先出现在脑海中,并且显得更简单。

调度器的当前实现接受几乎所有 Crontab 字符串格式,因此它可以是“*/5 2/3 * * *”、“20/5 2/3 1-2,4-7 * *”、“1,2,3,4,5 2/3 1-2,4-7 * *”。它还提供了通过向数组添加或删除元素来形成容器的方法,例如:addMinute(m)removeMinute(m) 等。

此外,调度器会打印出人类可读的字符串,例如:“每 3 小时发生一次,从 02:00 开始,每天,每月。”不幸的是,目前还不支持本地化。

另一个可能有趣的特性是计算直到特定日期的所有发生。为了避免在计算一年内非常详细的模式(例如“*/5 * * * *”)的日期时浏览器冻结,该操作被分成小块,并使用计时器延迟处理每个块,以便浏览器有机会切换到其他任务。默认块大小为 100 个元素,或者您可以将任何数字作为第三个参数传递给函数 getAllNextTill(...) 来指定自己的块大小。

/* set maxSyncLoadEl to -1 to have traditional loop */
this.getAllNextTill = function (endDate, callback, maxSyncLoadEl)
{
    var _allDates = Array();
    var _maxSyncLoadEl = typeof maxSyncLoadEl != 'undefined' && 
    	scheduler.isNumber(maxSyncLoadEl) ? maxSyncLoadEl : maxSyncLoadElDef;
    var _callback = callback;
    var _startDate = startDate;
    var _syncLoadedEl = 0;

    if (_startDate != null && _startDate instanceof Date)
    {
        var scope = this;
        (function loop()
        {
            try
            {
                while(_maxSyncLoadEl == -1 || _syncLoadedEl <= _maxSyncLoadEl)
                {
                    if (scope.previous())
                    {
                        var curDate = scope.getDateStamp();
                        if (_startDate < curDate)
                        {
                            _allDates.push(curDate);
                        }
                        else
                        {
                            if (callback)
                            {
                                callback(_allDates);
                            }
                            return;
                        }
                    }

                    _syncLoadedEl++;
                }

                _syncLoadedEl = 0;
                setTimeout(loop, 1);
            }
            catch (e)
            {
                throw e;
            }
        })();
    }
    else
    {
        throw new InvalidArgumentException();
    }
}
//

taskMangertask 对象负责调度和触发任务的所有工作。任务管理器封装了管理任务的方法。任务对象封装了在调度器设置的特定时间运行作业的方法,或者可以强制立即启动。

调度任务下一个发生示例

this.start = function ()
{
    _isStarted = true;
    continuouslyRun(true);
};

function continuouslyRun(skip)
{
    if (_isStarted)
    {
        if (!skip)
        {
            runOnce();
        }

        //schedule next time of running
        var currDate = new Date();
        _scheduler.setCurrentTimeExt(currDate);
        if (_scheduler.next())
        {
            var nextDate = _scheduler.getDateStamp();
            var timeout = nextDate.getTime() - currDate.getTime();
            _timerId = setTimeout(continuouslyRun, timeout);
        }
    }
};
function runOnce()
{
    _isRunning = true;

    if (_action)
    {
        if (!_args)
        {
            _action(scope);
        }
        else
        {
            _action(scope, _args);
        }
    }

    _isRunning = false;
};
//

数据模型

使用示例

目前,它只是一组用于添加/删除任务、枚举现有任务以及启动/停止管理器的 C# 方法。但我计划扩展 taskManager 接口,添加更多管理任务的方法,启用/禁用方法以及收集统计信息的方法。此外,根据浏览器中选择的语言或用户指定的语言,动态加载调度器的词典和人类可读的 string 格式将更加合适。

// 
            var tasks = new scheduler.taskManager();

            tasks.addTask(new scheduler.task("0/2  *   *   *   *", function (task, args)
            {
                //to do something
            }, "done"));

            tasks.addTask(new scheduler.task("0/10  *   *   *   *", function (task, args)
            {
                //to do something
            }, "done"));

            var _task = new scheduler.task("0/5  *   *   *   *", function (task, args)
            {
                //to do something
            }, "done");
            
            tasks.addTask(_task);

            tasks.enumerate(function (task, args)
            {
                //to do something
            });

            tasks.start();
            
            // to remove task from scheduling
            tasks.removeTask(_task);
//

如何使用

源文件 taskScheduler_src.zip 包含未混淆或最小化的源代码。要查看其工作原理,只需运行 index.html 文件。在源文件中,您将找到任务管理系统和调度器本身的文件,以及 utils 文件,还有几个演示其工作原理的文件。它可以被包装并用作 jQuery 插件,或者直接使用,它没有任何依赖关系,可以按原样使用。

© . All rights reserved.