14
15
const invariant = require('fbjs/lib/invariant');
16
17
-let caughtError = null;
18
-
19
-let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) {
20
- const funcArgs = Array.prototype.slice.call(arguments, 3);
21
- try {
22
- func.apply(context, funcArgs);
23
- } catch (error) {
24
- return error;
25
- }
26
- return null;
27
-};
28
-
29
-if (__DEV__) {
30
- /**
31
- * To help development we can get better devtools integration by simulating a
32
- * real browser event.
33
- */
34
- if (
35
- typeof window !== 'undefined' &&
36
- typeof window.dispatchEvent === 'function' &&
37
- typeof document !== 'undefined' &&
38
- typeof document.createEvent === 'function'
39
- ) {
40
- const fakeNode = document.createElement('react');
41
- let depth = 0;
42
-
43
- invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) {
44
- depth++;
45
- const thisDepth = depth;
46
- const funcArgs = Array.prototype.slice.call(arguments, 3);
47
- const boundFunc = function() {
48
- func.apply(context, funcArgs);
49
- };
50
- let fakeEventError = null;
51
- const onFakeEventError = function(event) {
52
- // Don't capture nested errors
53
- if (depth === thisDepth) {
54
- fakeEventError = event.error;
55
- }
56
- };
57
- const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`;
58
- window.addEventListener('error', onFakeEventError);
59
- fakeNode.addEventListener(evtType, boundFunc, false);
60
- const evt = document.createEvent('Event');
61
- evt.initEvent(evtType, false, false);
62
- fakeNode.dispatchEvent(evt);
63
- fakeNode.removeEventListener(evtType, boundFunc, false);
64
- window.removeEventListener('error', onFakeEventError);
65
- depth--;
66
- return fakeEventError;
67
- };
68
- }
69
-}
17
+const ReactErrorUtils = {
18
+ // Used by Fiber to simulate a try-catch.
19
+ _caughtError: null,
20
+ _hasCaughtError: false,
21
71
-let rethrowCaughtError = function() {
72
- if (caughtError) {
73
- const error = caughtError;
74
- caughtError = null;
75
- throw error;
76
- }
77
-};
22
+ // Used by event system to capture/rethrow the first error.
23
+ _rethrowError: null,
24
+ _hasRethrowError: false,
25
79
-const ReactErrorUtils = {
26
injection: {
27
injectErrorUtils(injectedErrorUtils: Object) {
28
invariant(
52
d: D,
53
e: E,
54
f: F,
109
- ): Error | null {
110
- return invokeGuardedCallback.apply(this, arguments);
55
+ ): void {
56
+ invokeGuardedCallback.apply(ReactErrorUtils, arguments);
57
},
58
59
/**
76
e: E,
77
f: F,
78
): void {
133
- const error = ReactErrorUtils.invokeGuardedCallback.apply(this, arguments);
134
- if (error !== null && caughtError === null) {
135
- caughtError = error;
79
+ ReactErrorUtils.invokeGuardedCallback.apply(this, arguments);
80
+ if (ReactErrorUtils.hasCaughtError()) {
81
+ const error = ReactErrorUtils.clearCaughtError();
82
+ if (!ReactErrorUtils._hasRethrowError) {
83
+ ReactErrorUtils._hasRethrowError = true;
84
+ ReactErrorUtils._rethrowError = error;
85
+ }
86
}
87
},
88
91
* we will rethrow to be handled by the top level error handler.
92
*/
93
rethrowCaughtError: function() {
144
- return rethrowCaughtError.apply(this, arguments);
94
+ return rethrowCaughtError.apply(ReactErrorUtils, arguments);
95
+ },
96
+
97
+ hasCaughtError: function() {
98
+ return ReactErrorUtils._hasCaughtError;
99
+ },
100
+
101
+ clearCaughtError: function() {
102
+ if (ReactErrorUtils._hasCaughtError) {
103
+ const error = ReactErrorUtils._caughtError;
104
+ ReactErrorUtils._caughtError = null;
105
+ ReactErrorUtils._hasCaughtError = false;
106
+ return error;
107
+ } else {
108
+ invariant(
109
+ false,
110
+ 'clearCaughtError was called but no error was captured. This error ' +
111
+ 'is likely caused by a bug in React. Please file an issue.',
112
+ );
113
+ }
114
},
115
};
116
117
+let invokeGuardedCallback = function(name, func, context, a, b, c, d, e, f) {
118
+ ReactErrorUtils._hasCaughtError = false;
119
+ ReactErrorUtils._caughtError = null;
120
+ const funcArgs = Array.prototype.slice.call(arguments, 3);
121
+ try {
122
+ func.apply(context, funcArgs);
123
+ } catch (error) {
124
+ ReactErrorUtils._caughtError = error;
125
+ ReactErrorUtils._hasCaughtError = true;
126
+ }
127
+};
128
+
129
+if (__DEV__) {
130
+ const ReactFeatureFlags = require('ReactFeatureFlags');
131
+
132
+ if (
133
+ typeof window !== 'undefined' &&
134
+ typeof window.dispatchEvent === 'function' &&
135
+ typeof document !== 'undefined' &&
136
+ typeof document.createEvent === 'function'
137
+ ) {
138
+ let preventDefault = true;
139
+
140
+ /**
141
+ * To help development we can get better devtools integration by simulating a
142
+ * real browser event.
143
+ */
144
+ const fakeNode = document.createElement('react');
145
+ let depth = 0;
146
+
147
+ const invokeGuardedCallbackDev = function(
148
+ name,
149
+ func,
150
+ context,
151
+ a,
152
+ b,
153
+ c,
154
+ d,
155
+ e,
156
+ f,
157
+ ) {
158
+ ReactErrorUtils._hasCaughtError = false;
159
+ ReactErrorUtils._caughtError = null;
160
+
161
+ depth++;
162
+ const thisDepth = depth;
163
+ const funcArgs = Array.prototype.slice.call(arguments, 3);
164
+ const boundFunc = function() {
165
+ func.apply(context, funcArgs);
166
+ };
167
+ const onFakeEventError = function(event) {
168
+ // Don't capture nested errors
169
+ if (depth === thisDepth) {
170
+ ReactErrorUtils._caughtError = event.error;
171
+ ReactErrorUtils._hasCaughtError = true;
172
+ }
173
+ if (preventDefault) {
174
+ event.preventDefault();
175
+ }
176
+ };
177
+ const evtType = `react-${name ? name : 'invokeguardedcallback'}-${depth}`;
178
+ window.addEventListener('error', onFakeEventError);
179
+ fakeNode.addEventListener(evtType, boundFunc, false);
180
+ const evt = document.createEvent('Event');
181
+ evt.initEvent(evtType, false, false);
182
+ fakeNode.dispatchEvent(evt);
183
+ fakeNode.removeEventListener(evtType, boundFunc, false);
184
+ window.removeEventListener('error', onFakeEventError);
185
+ depth--;
186
+ };
187
+
188
+ // Feature test the development version of invokeGuardedCallback
189
+ // before enabling.
190
+ let useInvokeGuardedCallbackDev;
191
+ if (ReactFeatureFlags.forceInvokeGuardedCallbackDev) {
192
+ // jsdom doesn't handle throwing null correctly (it fails when attempting
193
+ // to access the 'message' property) but we need the ability to test it.
194
+ // We use a feature flag to override the default feature test.
195
+ useInvokeGuardedCallbackDev = true;
196
+ } else {
197
+ try {
198
+ const err = new Error('test');
199
+ invokeGuardedCallbackDev(
200
+ null,
201
+ () => {
202
+ throw err;
203
+ },
204
+ null,
205
+ );
206
+ const A = ReactErrorUtils.clearCaughtError();
207
+
208
+ invokeGuardedCallbackDev(
209
+ null,
210
+ () => {
211
+ throw null;
212
+ },
213
+ null,
214
+ );
215
+ const B = ReactErrorUtils.clearCaughtError();
216
+
217
+ if (A === err && B === null) {
218
+ useInvokeGuardedCallbackDev = true;
219
+ }
220
+ } catch (e) {
221
+ useInvokeGuardedCallbackDev = false;
222
+ }
223
+ }
224
+
225
+ if (useInvokeGuardedCallbackDev) {
226
+ invokeGuardedCallback = invokeGuardedCallbackDev;
227
+ preventDefault = false;
228
+ }
229
+ }
230
+}
231
+
232
+let rethrowCaughtError = function() {
233
+ if (ReactErrorUtils._hasRethrowError) {
234
+ const error = ReactErrorUtils._rethrowError;
235
+ ReactErrorUtils._rethrowError = null;
236
+ ReactErrorUtils._hasRethrowError = false;
237
+ throw error;
238
+ }
239
+};
240
+
241
module.exports = ReactErrorUtils;