-
Notifications
You must be signed in to change notification settings - Fork 3
/
scheduler.es
93 lines (83 loc) · 2.62 KB
/
scheduler.es
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { sortBy, cloneDeep, partial } from 'lodash'
const INTERVAL = 1000
// Scheduler: Schedule one-time or interval tasks, without worrying the problem
// that setTimeout pauses when the computer falls aleep.
//
// Scheduler.prototype.schedule(func, time)
// Schedule an one-time task.
// func: (scheduledTime) => {}
// time: unix milliseconds.
// Scheduler.prototype.schedule(func, options)
// Schedule a complicated task.
// func: (scheduledTime) => {}
// options:
// time: unix milliseconds. The first start time, default Date.now()
// interval: milliseconds. Interval time, default being non-inverval
// allowImmediate: Boolean. If true, func will be executed immediately
// if opts.time < Date.now(). Otherwise it will find the next possible
// time according to opts.interval. Default true.
class Scheduler {
constructor() {
this._scheduleNextTick()
this._tasks = []
}
_scheduleNextTick() {
setTimeout(this._nextTick.bind(this), INTERVAL)
}
_tryTasks() {
const now = Date.now()
const nextNow = now + INTERVAL
while (this._tasks.length && nextNow >= this._tasks[0].next) {
const task = this._tasks.shift()
const { func, next: time, opts } = task
setTimeout(partial(func, time), Math.max(time - now, 0))
const next = this._nextTaskTime(opts, time, false)
if (next)
this._pushTask({
...task,
next,
})
}
}
_nextTick() {
this._tryTasks()
this._scheduleNextTick()
}
_parseOptions(rawOpts) {
const opts = cloneDeep(rawOpts)
if (typeof opts === 'number') return { time: opts }
if (typeof opts !== 'object' || !opts) throw new Error('Invalid scheduler time option')
if (!('time' in opts)) opts.time = Date.now()
if (!('allowImmediate' in opts)) opts.allowImmediate = true
return opts
}
_nextTaskTime(opts, now, allowImmediate) {
if (opts.time > now) {
return opts.time
}
if (!opts.interval) {
if (!allowImmediate) return
return opts.time
}
const { time, interval } = opts
const nextBeforeNow = Math.floor((now - time) / interval) * interval + time
if (allowImmediate) return nextBeforeNow
return nextBeforeNow + interval
}
_pushTask(task) {
this._tasks = sortBy(this._tasks.concat([task]), 'next')
}
schedule(func, rawOpts) {
const opts = this._parseOptions(rawOpts)
const next = this._nextTaskTime(opts, Date.now(), opts.allowImmediate)
if (!next) return
const task = {
opts,
next,
func,
}
this._pushTask(task)
this._tryTasks()
}
}
export default new Scheduler()