js 定时器

js定时器插件,支持跨页面定时器调用,定时器统一管理


/**
 *   使用示例
 *
 *  scheduler.addJob({
            job: function(){
                console.log("interval", Date.now())
            },
            cron: '0 0 0 18' // 每天下午六点执行  cron express millisecond second minute hour
        })
 *  cron express : 0 0 0 9  9:00 am
 *  0/10 * * * :  间隔 10  毫秒
 *  * 0/10 * * :   间隔10 秒 
 *  
 *  执行一次或者循环执行  arguments: (id|String 可忽略) job|Function delay|Number  unit| SCHEDULER_UNIT
 * 
 *  scheduler.(interval or once)(function(){
 *           console.log("interval", Date.now())
 *      }, 2, SCHEDULER_UNIT.SECOND)
 * 
 * 
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define(factory)
    } else {
        window.scheduler = factory()
    }
})(function () {
    'use strict'

    var storage = window.localStorage,
        triggerKey = 'TRIGGER_CACHE',
        fnTag = 'function:',
        utils = {
            exchange: function (arr, fn) { //排列组合 二维数组
                var results = []
                var result = []
                var exchange = function (arr, depth) {
                    for (var i = 0; i < arr[depth].length; i++) {
                        result[depth] = arr[depth][i]
                        if (depth != arr.length - 1) {
                            exchange(arr, depth + 1)
                        } else {
                            (fn || utils.noop).call(result)
                            results.push(result)
                        }
                    }
                }
                exchange(arr, 0)
                return results
            },
            id: function () {
                var s4 = function () {
                    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)
                }
                return (s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4())
            },
            isArray: function (obj) {
                return Object.prototype.toString.call(obj) === '[object Array]'
            },
            isString: function (obj) {
                return Object.prototype.toString.call(obj) === '[object String]'
            },
            isFunction: function (obj) {
                return Object.prototype.toString.call(obj) === '[object Function]'
            },
            isUndefined: function (obj) {
                return Object.prototype.toString.call(obj) === '[object Undefined]'
            },
            isObject: function (obj) {
                return Object.prototype.toString.call(obj) === '[object Object]'
            },
            translate: function (obj) {
                Object.keys(obj).forEach(function (key) {
                    if (utils.isObject(obj[key])) {
                        utils.translate(obj[key])
                    } else if (utils.isFunction(obj[key])) {
                        obj[key] = fnTag + obj[key].toString()
                    }
                })
            },
            recovery: function (obj) {
                Object.keys(obj).forEach(function (key) {
                    if (utils.isObject(obj[key])) {
                        utils.recovery(obj[key])
                    } else if (utils.isString(obj[key]) && obj[key].indexOf(fnTag) === 0) {
                        obj[key] = eval('(' + obj[key].substring(fnTag.length) + ')')
                    }
                })
            },
            stringify: function (obj) {
                obj = utils.extend({}, obj)
                utils.translate(obj)
                return JSON.stringify(obj);
            },
            parse: function (json) {
                var rs = JSON.parse(json)
                utils.recovery(rs)
                return rs
            },
            extend: function () {
                var rs = arguments[0] = arguments[0] || {}
                if (arguments.length <= 1) {
                    return rs
                } else {
                    if (!utils.isObject(arguments[1])) {
                        rs = arguments[1]
                    } else {
                        utils.each(arguments[1], function (k, v) {
                            if(v === null || undefined === v) return
                            !rs[k] && (rs[k] = {})
                            utils.isObject(v) ? (utils.extend(rs[k], v)) : (rs[k] = v)
                        })
                    }
                    for (var i = 2; i < arguments.length; i++) {
                        utils.extend(rs, arguments[i])
                    }
                }
                return rs
            },
            each: function (obj, fn) {
                obj = obj || {}
                fn = fn || utils.noop
                var isArray = utils.isArray(obj)
                for (var p in obj) {
                    if (fn.call(obj[p], isArray ? parseInt(p) : p, obj[p], obj) === false) break
                }
            },
            noop: function () {
            }
        },
        triggerStore = utils.parse(storage.getItem(triggerKey) || '{}'),
        persistent = function(){storage.setItem(triggerKey, utils.stringify(triggerStore))},
        console = window.console

    String.prototype.contains = function (search) {
        return this.indexOf(search) != -1
    }
    String.prototype.format = function (args) {
        var result = this;
        if (arguments.length > 0) {
            if (arguments.length == 1 && typeof (args) == "object") {
                for (var key in args) {
                    if (args[key] != undefined) {
                        var reg = new RegExp("({" + key + "})", "g");
                        result = result.replace(reg, args[key]);
                    }
                }
            } else {
                for (var i = 0; i < arguments.length; i++) {
                    if (arguments[i] != undefined) {
                        //var reg = new RegExp("({[" + i + "]})", "g");//这个在索引大于9时会有问题
                        var reg = new RegExp("({)" + i + "(})", "g");
                        result = result.replace(reg, arguments[i]);
                    }
                }
            }
        }
        return result;
    }

    /**
     * @param cellTime 1,2,3 0/1
     * @param unit
     * @constructor
     */
    function CronTimeCell(cellTime, unit) {
        this.isValid = true //是否有效
        if (!cellTime.match('\\d')) {
            this.isValid = false
        } else{
            this.unit = parseInt(unit) //单位
            if (cellTime.contains('/')) {
                this.type = CronTimeCell.CELL_TIME_TYPE.STEP
                this.step = parseInt(cellTime.split('/')[1])
            } else {
                this.type = CronTimeCell.CELL_TIME_TYPE.INTERVAL
                this.step = parseInt(cellTime)
            }
        }
    }

    CronTimeCell.CELL_TIME_TYPE = {
        INTERVAL: 1, // 设置具体某个时间点 譬如说每分钟的 第几秒 每小时的第几分钟执行 等等
        STEP: 2 //设置时间间隔执行 如间隔多少秒执行
    }

    function CronTime(express) {
        var arr = express.trim().replace(/\s+/g, ' ').split(' ')
        while (arr.length < 4) {
            arr.push('?')
        }
        var self = this
        self.type = CronTimeCell.CELL_TIME_TYPE.STEP
        var timeArray = []
        try {
            utils.each(arr, function (i, cell) {
                var cronTimeCell = new CronTimeCell(cell, i + 1);
                if (cronTimeCell.type === CronTimeCell.CELL_TIME_TYPE.INTERVAL) {
                    self.type = cronTimeCell.type
                }
                timeArray.push(cronTimeCell)
            })
            this.timeArray = timeArray
        } catch (e) {
            console.error('express [{0}] error'.format(express))
        }
    }

    var setMethods = ['setMilliseconds', 'setSeconds', 'setMinutes', 'setHours', 'setDate']
    var getMethods = ['getMilliseconds', 'getSeconds', 'getMinutes', 'getHours', 'getDate']

    CronTime.prototype.getNextTime = function (previous) {
        var nextTime
        var now = new Date()
        previous = previous || Date.now()
        if (this.type === CronTimeCell.CELL_TIME_TYPE.STEP) {
            var count = 0
            this.timeArray.forEach(function (time) {
                time.isValid && (count += [0, 1, 1000, 60000, 360000][time.unit] * time.step)
            })
            return previous + count
        } else {
            var maxUnit = CronTime.CELL_TIME_UNIT.MILLISECOND
            this.timeArray.forEach(function (time) {
                if (time.isValid) {
                    now[setMethods[time.unit - 1]](time.step)
                    maxUnit = time.unit
                }
            })
            while (now.getTime() <= previous) {
                now[setMethods[maxUnit]](now[getMethods[maxUnit]]() + 1)
            }
            return now.getTime();
        }
    }

    CronTime.CELL_TIME_UNIT = {
        MILLISECOND: 1,
        SECOND: 2,
        MINUTE: 3,
        HOUR: 4,
        DAY: 5
    }

    function Trigger(options) {
        var opts = {
            id: utils.id(),//定时器id
            sid: -1, //系统定时器id
            count: 0, //被触发的次数
            job: utils.noop,//被执行的任务
            state: Trigger.TRIGGER_STATE.NORMAL,
            cron: '* 0/1 * ?',//触发器时间表达式
            once: false,
            previousFireTime: null,//上一次触发的时间
            nextFireTime: null, //下一次触发的时间
            onError: utils.noop,//任务调用出错
            onSuccess: utils.noop,//成功提交触发
            onFire: utils.noop //触发之前
        }
        opts = utils.extend(opts, options)
        var self = this
        self.id = opts.id
        self.proxy = opts//TODO 可考虑构造代理对象
        self.cron = new CronTime(opts.cron)
        persistent()
    }

    Trigger.TRIGGER_STATE = {
        NORMAL: 0, //正常
        RUNNING: 1, //执行中
        COMPLETE: 2, //已提交
        ERROR: 3, //出错
        STOP: 4 //停止
    }

    Trigger.prototype.fire = function () {
        var self = this
        if (self.proxy.state === Trigger.TRIGGER_STATE.STOP) {
            console.warn('Trigger [{0}] is stop'.format(self.id))
            return
        }
        self.proxy.state = Trigger.TRIGGER_STATE.RUNNING
        self.proxy.onFire.call(self)
        try {
            self.proxy.job.call(self)
            self.proxy.state = Trigger.TRIGGER_STATE.COMPLETE
        } catch (e) {
            self.proxy.state = Trigger.TRIGGER_STATE.ERROR
            self.proxy.onError.call(self, self.proxy.state)
        }
        self.proxy.count++
        self.proxy.previousFireTime = Date.now()
        self.proxy.nextFireTime = self.cron.getNextTime()
        window.clearTimeout(self.sid)
        if (self.proxy.once) {
            self.proxy.state = Trigger.TRIGGER_STATE.STOP
        } else {
            self.proxy.sid = setTimeout(function () {
                self.fire()
            }, self.proxy.nextFireTime - self.proxy.previousFireTime)
        }
        triggerStore[self.id] = self.proxy
        persistent()
    }

    Trigger.prototype.start = function () {
        var self = this
        self.proxy.nextFireTime = self.cron.getNextTime()
        self.proxy.sid = setTimeout(function () {
            self.fire()
        }, self.proxy.nextFireTime - Date.now())
        persistent()
        return self
    }

    Trigger.prototype.stop = function () {
        var self = this
        self.proxy.state = Trigger.TRIGGER_STATE.STOP
        window.clearTimeout(this.proxy.sid)
        this.persistent()
    }

    Trigger.prototype.remove = function () {
        var self = this
        window.clearTimeout(self.proxy.sid)
        delete triggerStore[self.id]
        persistent()
    }

    Trigger.prototype.persistent = function () {
        storage.setItem(triggerKey, utils.stringify(triggerStore))
    }

    function Scheduler() {
        var self = this
        self.triggers = {}
        utils.each(triggerStore, function (id, opts) {
            self.triggers[id] = new Trigger(opts).start()
        })
    }

    Scheduler.prototype.trigger = function (id) {
        this.triggers[id].fire()
    }

    /**
     *
     * @param options object
     */
    Scheduler.prototype.addJob = function (options) {
        var opts = {
            id: null,
            once: false,
            job: utils.noop,
            cron: null
        }
        opts = utils.extend(opts, options)
        var trigger = new Trigger(opts)
        trigger.start()
        this.triggers[trigger.id] = trigger
    }

    function getOptions (array) {
        var id
        if(utils.isString(array[0])) {
            id = array[0]
            array = array.slice(1)
        }
        this.remove(id)
        return {
            id: id,
            job: array[0],
            cron: array[1]  ? '0/{0} ? ? ?'.format(array[1] * [0, 1, 1000, 60000, 360000][array[2] > 0 && array[2] < 5 ? array[2] : 1]) : null,
            once: array[array.length - 1]
        }
    }

    /**
     *
     * @param id String|Function(job)
     * @param job
     * @param dealy
     * @param unit
     */
    Scheduler.prototype.once = function () {
        var array = Array.prototype.slice.call(arguments)
        array.push(true)
        this.addJob(getOptions.call(this, array))
    }
    /**
     *
     * @param job Function
     * @param interval number
     * @param unit SCHEDULER_UNIT 1,2,3,4
     */
    Scheduler.prototype.interval = function (id, job, interval, unit) {
        var array = Array.prototype.slice.call(arguments)
        array.push(false)
        this.addJob(getOptions.call(this, array))
    }
    Scheduler.prototype.each = function (fn) {
        utils.each(this.triggers, fn)
    }
    Scheduler.prototype.clear = function () {
        utils.each(this.triggers, function () {
            this.remove()
        })
        this.triggers = {}
    }
    Scheduler.prototype.remove = function (id) {
        id&&this.triggers[id].remove()
    }
    Scheduler.prototype.start = function (id) {
        id&&this.triggers[id].start()
    }
    Scheduler.prototype.stop = function (id) {
        id&&this.triggers[id].stop()
    }
    window.SCHEDULER_UNIT = {
        MILLISECOND: 1,
        SECOND: 2,
        MINUTE: 3,
        HOUR: 4
    }
    var scheduler = new Scheduler()
    scheduler.version = 'version 2.0'
    return scheduler
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值