-
Notifications
You must be signed in to change notification settings - Fork 106
/
Copy pathbatch.js
131 lines (116 loc) · 3.74 KB
/
batch.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
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
/* eslint camelcase: 0 */
// react-platform is set to either react-dom or react-native during test and execution
// eslint-disable-next-line import/no-unresolved
import { unstable_batchedUpdates } from 'react-platform';
import { globalObj } from './utils';
import scheduler from './scheduler';
// this runs the passed function and delays all re-renders
// until the function is finished running
// react renders are batched by unstable_batchedUpdates
// autoEffects and other custom reactions are batched by our scheduler
export function batch(fn, ctx, args) {
// do not apply scheduler logic if it is already applied from a parent function
// it would flush in the middle of the parent's batch
if (scheduler.isOn) {
return unstable_batchedUpdates(() => fn.apply(ctx, args));
}
try {
scheduler.on();
return unstable_batchedUpdates(() => fn.apply(ctx, args));
} finally {
scheduler.flush();
scheduler.off();
}
}
// this creates and returns a batched version of the passed function
// the cache is necessary to always map the same thing to the same function
// which makes sure that addEventListener/removeEventListener pairs don't break
const cache = new WeakMap();
function batchFn(fn) {
if (typeof fn !== 'function') {
return fn;
}
let batched = cache.get(fn);
if (!batched) {
batched = new Proxy(fn, {
apply(target, thisArg, args) {
return batch(target, thisArg, args);
},
});
cache.set(fn, batched);
}
return batched;
}
function batchMethodCallbacks(obj, method) {
const descriptor = Object.getOwnPropertyDescriptor(obj, method);
if (
descriptor &&
descriptor.writable &&
typeof descriptor.value === 'function'
) {
obj[method] = new Proxy(descriptor.value, {
apply(target, ctx, args) {
return Reflect.apply(target, ctx, args.map(batchFn));
},
});
}
}
// batched obj.addEventListener(cb) like callbacks
function batchMethodsCallbacks(obj, methods) {
methods.forEach(method => batchMethodCallbacks(obj, method));
}
function batchMethod(obj, method) {
const descriptor = Object.getOwnPropertyDescriptor(obj, method);
if (!descriptor) {
return;
}
const { value, writable, set, configurable } = descriptor;
if (configurable && typeof set === 'function') {
Object.defineProperty(obj, method, {
...descriptor,
set: batchFn(set),
});
} else if (writable && typeof value === 'function') {
obj[method] = batchFn(value);
}
}
// batches obj.onevent = fn like calls and store methods
export function batchMethods(obj, methods) {
methods = methods || Object.getOwnPropertyNames(obj);
methods.forEach(method => batchMethod(obj, method));
return obj;
}
// do a sync batching for the most common task sources
// this should be removed when React's own batching is improved in the future
// batch timer functions
batchMethodsCallbacks(globalObj, [
'setTimeout',
'setInterval',
'requestAnimationFrame',
'requestIdleCallback',
]);
if (globalObj.Promise) {
batchMethodsCallbacks(Promise.prototype, ['then', 'catch']);
}
// Event listener batching causes an input caret jumping bug:
// https://github.com/RisingStack/react-easy-state/issues/92.
// This part has to be commented out to prevent that bug.
// React batches setStates in its event listeners anyways
// so this commenting this part out is not a huge issue.
// batch addEventListener calls
/* if (globalObj.EventTarget) {
batchMethodsCallbacks(EventTarget.prototype, [
'addEventListener',
'removeEventListener',
]);
} */
// this batches websocket event handlers
if (globalObj.WebSocket) {
batchMethods(WebSocket.prototype, [
'onopen',
'onmessage',
'onerror',
'onclose',
]);
}
// HTTP event handlers are usually wrapped by Promises, which is covered above