forked from ianstormtaylor/slate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexecutor.js
96 lines (80 loc) · 2.35 KB
/
executor.js
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
94
95
96
/**
* A function that does nothing
* @return {Function}
*/
function noop() {}
/**
* Creates an executor like a `resolver` or a `deleter` that handles
* delayed execution of a method using a `requestAnimationFrame` or `setTimeout`.
*
* Unlike a `requestAnimationFrame`, after a method is cancelled, it can be
* resumed. You can also optionally add a `timeout` after which time the
* executor is automatically cancelled.
*/
export default class Executor {
/**
* Executor
* @param {window} window
* @param {Function} fn - the function to execute when done
* @param {Object} options
*/
constructor(window, fn, options = {}) {
this.fn = fn
this.window = window
this.resume()
this.onCancel = options.onCancel
this.__setTimeout__(options.timeout)
}
__call__ = () => {
// I don't clear the timeout since it will be noop'ed anyways. Less code.
this.fn()
this.preventFurtherCalls() // Ensure you can only call the function once
}
/**
* Make sure that the function cannot be executed any more, even if other
* methods attempt to call `__call__`.
*/
preventFurtherCalls = () => {
this.fn = noop
}
/**
* Resume the executor's timer, usually after it has been cancelled.
*
* @param {Number} [ms] - how long to wait by default it is until next frame
*/
resume = ms => {
// in case resume is called more than once, we don't want old timers
// from executing because the `timeoutId` or `callbackId` is overwritten.
this.cancel()
if (ms) {
this.mode = 'timeout'
this.timeoutId = this.window.setTimeout(this.__call__, ms)
} else {
this.mode = 'animationFrame'
this.callbackId = this.window.requestAnimationFrame(this.__call__)
}
}
/**
* Cancel the executor from executing after the wait. This can be resumed
* with the `resume` method.
*/
cancel = () => {
if (this.mode === 'timeout') {
this.window.clearTimeout(this.timeoutId)
} else {
this.window.cancelAnimationFrame(this.callbackId)
}
if (this.onCancel) this.onCancel()
}
/**
* Sets a timeout after which this executor is automatically cancelled.
* @param {Number} ms
*/
__setTimeout__ = timeout => {
if (timeout == null) return
this.window.setTimeout(() => {
this.cancel()
this.preventFurtherCalls()
}, timeout)
}
}