npm模块corn源码分析

About corn

npm地址:https://www.npmjs.com/package/cron
用途:如Linux系统有一个定时执行任务的工具cron,cron作者实现了类似的功能。

使用这个模块

  • 简单实用
/**
 * Created by bamboo on 2016/4/15.
 */

var cronJob = require('cron').CronJob;
var job1 = new cronJob("* * * * * *", function () {
    "use strict";
    console.log("每1秒执行一次。。。。")
});
job1.start();
var job2 = new cronJob("*/5 * * * * *", function () {
    "use strict";
    console.log("每5秒执行一次。。。")
});
job2.start();

执行

npm install cron
node cron.js

执行结果

每1秒执行一次。。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
每5秒执行一次。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
每1秒执行一次。。。。
  • 参数使用
var CronJob = require('cron').CronJob;
var job = new CronJob({
  cronTime: '00 30 11 * * 1-5',
  onTick: function() {
    /*周一到周五,每天中午十一点半执行任务
     * Runs every weekday (Monday through Friday)
     * at 11:30:00 AM. It does not run on Saturday
     * or Sunday.
     */
  },
  start: false,
  timeZone: 'America/Los_Angeles'
});
job.start();
  • 定时更新任务
    代码源自于实际项目,用于定时更新任务,即定时执行node update/all.js,其中根据pipe的形式,将更新代码输出。值得注意的是,在子进程的运行结果将被放在系统缓存中(最大为200KB),
var spawn = require("child_process").spawn;
var job = new cronJob("* * */30 * * *", function () {
    "use strict";
    console.log("开始执行定时更新任务");
    var update = spawn(process.execPath, [path.resolve(__dirname, 'update/all.js')]);
    update.stdin.pipe(process.stdout);
    update.stderr.pipe(process.stderr);
    //进程结束时触发close事件
    update.on('close', function (code) {
        console.log("更新结束,代码为=%d", code)
    })
});
job.start();

接口

CronJob

constructor(cronTime, onTick, onComplete, start, timezone, context, runOnInit) - 第一个参数可以为一个JSONJson的内容为如上的参数
cronTime - [REQUIRED] -规定时间执行程序. 
onTick - [REQUIRED] - 要执行的程序.
onComplete - [OPTIONAL] - 当这个任务执行完成时候执行的完成程序,在jobstop()的时候使用
start - [OPTIONAL] - 如果为true则该job立即执行
timeZone - [OPTIONAL] - 时区,如'America/Los_Angeles'会根据时区修正为正确的时间
context - [OPTIONAL] - 在执行ontick函数中使用.
runOnInit - [OPTIONAL] - 如果为true则马上执行ontick函数
start - 开始执行任务
stop - 结束执行任务
CronTime

constructor(time)
time - [REQUIRED] - 设置执行ontick的时间.时间格式可以为corn syntaxJs Date对象

CronJob

通过npm下载得到的源码,经过查看,发现主要代码的位置位于lib/cron.js中,按照阅读源码的习惯,首先找到exports的位置,然后看这个模块到底导出了什么?

if (exports) {
//直接返回一个任务
    exports.job = function (cronTime, onTick, onComplete) {
        return new CronJob(cronTime, onTick, onComplete);
    }
    //返回定时时长
    exports.time = function (cronTime, timeZone) {
        return new CronTime(cronTime, timeZone);
    }
    //返回下次执行的时间
    exports.sendAt = function (cronTime) {
        return exports.time(cronTime).sendAt();
    }
    exports.timeout = function (cronTime) {
        return exports.time(cronTime).getTimeout();
    }
    //任务类
    exports.CronJob = CronJob;
    exports.CronTime = CronTime;
}

看到使用处的new cronJob(“* * * * * *”)很想知道这到底意味着什么,查看源码得知CronTime的语法格式为f1 f2 f3 f4 f5 f6分别代表

  timeUnits = ['second', 'minute', 'hour', 'dayOfMonth', 'month', 'dayOfWeek'],
  //秒钟,分钟,小时,一个月份中的第几天,月份,一个星期中的第几天
  • 当值为 *时,表示每一个单位都执行一次
  • 当值为a-b时,表示每a-b单位这段时间内执行一次
  • 当值为*/n ,表示每隔n个单位时间执行一次
  • 当值为a-b/n ,表示从a-b这段时间内每隔n个单位执行一次

并存在对这些数值大小的限制,和日常中想的一样,限制的值如下

CronTime.constraints = [
    [0, 59],
    [0, 59],
    [0, 23],
    [1, 31],
    [0, 11],
    [0, 6]
];

CornJob构造函数

function CronJob(cronTime, onTick, onComplete, startNow, timeZone, context, runOnInit) {
    var _cronTime = cronTime;
    if (typeof cronTime != "string" && arguments.length == 1) {
        //当第一个参数为一个对象的时候,其他的参数都在这个对象中
        onTick = cronTime.onTick;
        onComplete = cronTime.onComplete;
        context = cronTime.context;
        startNow = cronTime.start || cronTime.startNow || cronTime.startJob;//由这3值决定任务是否马上执行
        timeZone = cronTime.timeZone;//时区
        runOnInit = cronTime.runOnInit;
        _cronTime = cronTime.cronTime;
    }

    this.context = (context || this);
    this._callbacks = [];//ontick存放的位置
    //onComplete,可能是一个函数,也可能是一个如shell(ls -a )的命令
    //所有需要将解析
    this.onComplete = command2function(onComplete);
    this.cronTime = new CronTime(_cronTime, timeZone);
    //添加回调
    addCallback.call(this, command2function(onTick));
    //如果true则马上执行ontick
    if (runOnInit) fireOnTick.call(this);
    //如果为true则马上执行job 注意比较2者的区别
    if (startNow) start.call(this);
    return this;
}

command2function 顾名思义

function command2function(cmd) {
    switch (typeof cmd) {
        case 'string':
            var args = cmd.split(' ');
            var command = args.shift();//删除第一个并返回元素
            cmd = spawn.bind(undefined, command, args);
            break;
        case 'object':
            var command = cmd && cmd.command;
            if (command) {
                var args = cmd.args;
                var options = cmd.options;
                console.log(command);
                cmd = spawn.bind(undefined, command, args, options);
            }
            break;
    }
    return cmd
}

注意:代码中出现的spawn,正如大家所想的,那正是child_process模块的spawn方法。

var spawn = require('child_process').spawn;

关于这个child_process模块在nodeapi中可以查到
https://nodejs.org/download/docs/v5.7.0/api/child_process.html

关于addCallback函数只是一个简单的

var addCallback = function (callback) {
    if (typeof callback == 'function') this._callbacks.push(callback);
}

CronJob.prototype.start 任务开始的地方

var start = function () {
    if (this.running) return;//正在运行则直接返回
    var MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait.//等待运行的最长时间
    var self = this;
    var timeout = this.cronTime.getTimeout();//获取间隔时长
    var remaining = 0;

    if (this.cronTime.realDate) this.runOnce = true;
    //这个函数用来检查是否需要再休眠一次,和执行真实地回调逻辑,当时间到了的时候
    function callbackWrapper() {
        //如果仍然需要休眠,则计算休眠时长并休眠,这个操作将会消耗几ms的时间,这样的几ms的休眠时间对于定时时长长达几个month的来说,几乎没有影响。
        //如果有
        if (remaining) {
            if (remaining > MAXDELAY) {
                remaining -= MAXDELAY;
                timeout = MAXDELAY;
            } else {
                timeout = remaining;
                remaining = 0;
            }
            self._timeout = setTimeout(callbackWrapper, timeout);//timeout时间后再执行这个函数
            //,没有,说明以及到了执行ontick函数的时候了
        } else {
            self.running = false;
            //start before calling back so the callbacks have the ability to stop the cron job
            //在回调之前开始,这样做能保证在进行回调的时候有能力将job停止
            if (!(self.runOnce)) self.start();
            self.fireOnTick();
            //等价于 for (var i = (self._callbacks.length - 1); i >= 0; i--)
            //self._callbacks[i].call(self.context, self.onComplete);
        }
    }

    if (timeout >= 0) {
        this.running = true;

        // Don't try to sleep more than MAXDELAY ms at a time.
        //不要尝试在休眠时间达到最大值。
        if (timeout > MAXDELAY) {
            remaining = timeout - MAXDELAY;
            timeout = MAXDELAY;
        }

        this._timeout = setTimeout(callbackWrapper, timeout);
    } else {
        this.stop();
    }

任务开始的地方:仔细分析这个函数的构成,可以发现,任务是根据timeout是否大于0来判断任务是否还在运行,如果正在运行的话,则将在timeout时间后调用ontick,此时将self.running设置成false,故可以猜测在stop()方法中,肯定设置了timeout的值,这样才会“停下来”。注意在源码的451行中this._timeout = setTimeout(callbackWrapper, timeout);
对于这个返回值, 将根据这个值在stop中执行clearTimeout操作
另一个值得注意的是:callbackWrapper这个函数做了什么工作,这个函数简单地来说就是检查是否需要再休眠,然后执行ontick函数,具体做法就是如果remaining,则说明还需要设置执行再休眠,重新timeout的值,然后执行ontick函数。多说无益,如果有兴趣学习下源码比什么源码分析都强呢。。

CronJob.prototype.stop 任务终结的地方

任务终结地如此简单

CronJob.prototype.stop = function () {
    if (this._timeout)
        clearTimeout(this._timeout);//清除
    this.running = false;
    if (typeof this.onComplete == 'function') this.onComplete();//如果是函数,则执行function
}

任务终结的时候,在最开始new CronJob的时候可能会对任务结束时进行回调,onComplete函数就是这个回调函数。

总结

只做了部分源码分析,关于根据CronTime的内容,将在过些时间进行分析,其中还包括一个解析时间的moment模块。

你可能感兴趣的:(javascript,Node,npm)