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
});