-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmanage-calls.js
More file actions
207 lines (158 loc) · 6.1 KB
/
manage-calls.js
File metadata and controls
207 lines (158 loc) · 6.1 KB
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
'use strict'
const Promise = require('bluebird')
module.exports = {
//TODO: Add tests for delay
/**
* Replaces a method of provided object with a proxy, that will chain consecutive calls:
* So the orginal method will be called right after all previous calls have been resolved or rejected.
*
* The proxy method will allways wrap the original method in a Promise.
*
* You can provide an array of method names to serialize multiple methods at once.
*
* This may help to prevent timing conflicts of multiple calls.
*
* The orginial mthod can be restored with obj.[method_name].restore().
*
*
* @memberof module:docloop
*
* @param {Object} obj Target object
* @param {string | string[]} method_names Name(s) of the method(s) whose calls should be serialized.
* @param {Number} delay Number of milliseconds to wait for until the next call.
*
* @throws {TypeError} If one of the method names does not point to a function.
*
* @return {} undefined
*
*/
serializeCalls: function(obj, method_names, delay){
var original_methods = {},
chain = Promise.resolve()
if(!method_names.forEach) method_names = [method_names]
function chainRun(method_name, ...args){
if(!chain.isPending()) chain = Promise.resolve()
return chain = chain
.catch( e => console.log(e) ) /*TODO*/
.then( () => Promise.delay(delay || 0))
.then( original_methods[method_name].bind(obj, ...args) )
}
function restore(){
method_names.forEach( method_name => {
obj[method_name] = original_methods[method_name]
})
}
method_names.forEach( method_name => {
if(typeof obj[method_name] != 'function') throw new TypeError("Not a method '"+method_name+ "' on "+obj.toString())
original_methods[method_name] = obj[method_name]
obj[method_name] = chainRun.bind(this, method_name)
obj[method_name].restore = restore
})
},
/**
* Replaces a method of provided object with a proxy,
* that will prevent multiple calls to the original method with the same arguments
* while waiting for a response.
*
* The proxy method will allways wrap the original method in a Promise.
*
* If a previous call with the same arguments is still pending the proxy will return the corresponding promise.
*
* This may help to prevent multiple redudant API calls.
* (e.g. when you dont expect the result of an API call to change while it's pending)
*
* Two sets of arguments are considered equal if their JSON representaions are equal.
*
* The orginial method can be restored with obj[method_name].restore().
*
* @memberof module:docloop
*
* @param {Object} obj Target object
* @param {string} method_name Name of the method whose calls should be callated
* @param {Number} [force_call_after=1000] After this time (in milliseconds) the proxy will call the original method, regardless of previous calls still pending.
*
* @throws {TypeError} If method_name does not point to a function.
*
* @return {} undefined
*/
collateCalls: function(obj, method_name, force_call_after = 1000){
if(typeof obj[method_name] != 'function') throw new TypeError("collateCalls(): Not a method '"+method_name+ "' on "+obj.toString())
var original_fn = obj[method_name],
scheduled_runs = {}
function requestRun(...args){
var str = JSON.stringify(args)
if(!scheduled_runs[str] || !scheduled_runs[str].isPending()){
scheduled_runs[str] = Promise.resolve( original_fn.apply(obj, args) )
setTimeout( () => scheduled_runs[str] = undefined, force_call_after)
}
return scheduled_runs[str]
}
function restore(){
obj[method_name] = original_fn
}
obj[method_name] = requestRun
obj[method_name].restore = restore
},
/**
* Replaces a method of provided object with a proxy,
* that will cache the return value of the original method for the givin period of time.
* If the proxy is called the original method will be called only if there is no cached value.
*
* The proxy method will allways wrap the original method in a Promise.
*
* This may help to prevent redundant API calls (e.g. if you know that the result of an API call will not change in the next 10 seconds)
*
* Two calls with different arguments will be cached separately.
* Two sets of arguments are considered equal if their JSON representaions are equal.
*
* The orginial method can be restored with obj[method_name].restore().
*
* @memberof module:docloop
*
* @param {Object} obj Target object
* @param {String} method_name Name of the method whose calls should be cached.
* @param {Number} ttl=1000 Time to live for the cache values in milliseconds.
*
* @throws {TypeError} If method_name does not point to a function.
*
* @return {} undefined
*/
cacheCalls: function(obj, method_name, ttl = 1000){
if(typeof obj[method_name] != 'function') throw new TypeError("cacheCalls(): Not a method '"+method_name+ "' on "+obj.toString())
var original_fn = obj[method_name],
scheduled_runs = {}
function requestRun(...args){
var str = JSON.stringify(args)
if(!scheduled_runs[str]){
scheduled_runs[str] = Promise.resolve( original_fn.apply(obj, args) )
setTimeout( () => scheduled_runs[str] = undefined , ttl)
}
return scheduled_runs[str]
}
function restore(){
obj[method_name] = original_fn
delete obj[method_name].restore
}
obj[method_name] = requestRun
obj[method_name].restore = restore
},
/**
* Todo
*
*/
limitCalls: function(obj, method_name, ttl){
if(typeof obj[method_name] != 'function') throw new TypeError("limitCalls(): Not a method '"+method_name+ "' on "+obj.toString())
var original_fn = obj[method_name],
timeout = undefined
function requestRun(){
if(timeout) clearTimeout(timeout)
timeout = setTimeout( () => original_fn.apply(obj), ttl)
}
function restore(){
obj[method_name] = original_fn
delete obj[method_name].restore
}
obj[method_name] = requestRun
obj[method_name].restore = restore
}
}