まあコンパイルしてJavaScriptに展開すると100行超えるんですが。
もうちょい徹底すれば25行くらいにはなりそうだけど、可読性を失う自己満足になるのでこの辺で自重。
class Backbone.Cron
constructor: (@owner, crontabs, autostart = true) ->
seqArray = (from, to) -> _.map(new Array(to - from + 1).join().split(','), (v, i, a) -> i + from)
parseCrontab = (crontab) =>
ctArray = _.map(crontab.split(/\s+/), (v, i, a) ->
return v if i is 6 or v is '*'
v = v.replace /(\d+)-(\d+)/g, (vm, vf, vt) -> seqArray(Number(vf), Number(vt)).join()
v = v.replace /\*\/(\d+)/g, (vm, vi) -> Number(vi) * -1
_.map(v.split(','), (vv, vi, va) -> Number vv).sort())
unit: @_makeunit ctArray
exec: ctArray[6]
@items = {}
@items[label] = _.extend parseCrontab(ct), { crontab: ct, pause: false } for label, ct of crontabs
@owner.bind 'remove', () => @stop() unless @owner.model?
@start() if autostart
start: () =>
@unixtime = parseInt(new Date() / 1000, 10)
@timer = setInterval () =>
d = new Date(++@unixtime * 1000)
now = @_makeunit [ d.getSeconds(), d.getMinutes(), d.getHours(), d.getDate(), d.getMonth() + 1, d.getDay() ]
for label, item of @items when not item.pause
@owner[item.exec] label, d if _.every item.unit, (v, k ,o) -> v? and (v is '*' or _.some(v, (vv, vi, va) -> if vv < 0 then now[k] % (vv * -1) is 0 else vv is now[k]))
, 1000
stop: () => clearInterval @timer
on: (label) => @items[label].pause = false
off: (label) => @items[label].pause = true
_makeunit: (array) => _.object [ 'second', 'minute', 'hour', 'day', 'month', 'weekday' ], array
使い方
まず上記ソースをコピペしてJavaScriptにコンパイルしたファイルをbackbone.jsの後にロードしてください。
<script type="text/javascript" src="/path/to/backbone.js"></script>
<script type="text/javascript" src="<上記ソースをコピペしてjsにコンパイルしたファイル>"></script>
Backbone.~.extend
のinitialize()
でBackbone.Cronオブジェクトを作成します。
var HogeModel = Backbone.Model.extend({
initialize: function () {
this.cron = new Backbone.Cron(this, {
test1: '0,45,20-23 * * * * * test1',
test2: '39 */6,10,20,40,50 * * * * test2',
test3: '0 */5 */2 * * * test2'
});
},
test1: function (label, now) {
console.log(label, now);
},
test2: function (label, now) {
console.log(label, now);
}
});
メソッド
new Backbone.Cron(<関連付けるオブジェクト>, <crontab設定>[, 自動スタート設定]);
引数1: 関連付けるオブジェクト
引数2のcrontabで指定したメソッドを実行させるオブジェクトです。大抵の場合はthis
で良いと思います(上記の例ではthis
= HogeModelオブジェクト)。
引数2: crontab設定
ラベル
:crontab
のペアからなる連想配列を指定します。crontabは
秒
分
時
日
月
曜日
実行するメソッド
の順に半角スペース区切りで指定します。一通りのcrontab書式が指定可能です。
ここで指定したメソッドが呼び出される時には(ラベル
、現在日時のDateオブジェクト
)の2つが引数で渡されます。
引数3: 自動スタート設定
デフォルトではBackbone.Cronをnewした時点でタイマーが作動します。とりあえずnewはするけど実行はちょっと後でという場合にはfalse
を指定してください。そして必要になったらstart()
メソッドでタイマーを作動させてください。
start()
作成したBackbone.Cronオブジェクトのタイマーを作動させます。
stop()
Backbone.Cronオブジェクトのタイマーを停止させます。crontabで登録した全ての処理がストップされます。
off(<ラベル>)
指定したラベルの処理だけ実行させないようにします。
on(<ラベル>)
off()
で止めたラベルの処理を再度実行させるようにします。
注意点
Backbone.Cronを仕込んだオブジェクトを削除する前には必ずstop()
メソッドでタイマーを止めてください。そうしないとオブジェクトを削除した後にもタイマー処理が動き続け、メモリリークが発生します。
なお、Backbone.Cronの内部では、remove
イベントでstop()
が発生するようにbindしているので、Backbone.Modelに関連付けられたBackbone.Cronオブジェクトに関しては、Model削除時に自動的にタイマーが停止されます。
ただし、Collection.reset()
の場合、(Backbone.jsバージョン1.0.0の段階では)Modelに対してremove
イベントが発生しないのでタイマーが停止されません。そこで、Backbone.Collection.extendでreset()
の処理を以下の様に拡張しておけば、reset()
時にremove
イベントが発火されるようになります。
var HogeCollection = Backbone.Collection.extend({
model: HogeModel,
reset: function (models, options) {
this.each(function (model) {
model.trigger('remove');
});
return HogeCollection.__super__.reset.apply(this, arguments);
},
最後に
Backbone.jsに特化しているのはremove
イベントのbindだけなので、この部分を潰せばBackbone.jsに限らず、色々なオブジェクトにCron処理を実装する事ができます(underscore.js依存ですが)。
ちなみにモダンブラウザとIE7以上で動作確認済みです。