Refactor ReactErrorUtils (#13406)
* Refactor ReactErrorUtils * Remove unnecessary assignments
Dan Abramov committed
Aug 15, 2018 at 19:02 UTC
83e446e1d846cc0fef4aae473ab8244f6e163a34
10 files changed
+179
-160
packages/events/EventPluginHub.js
+2
-2
@@ -6,7 +6,7 @@
6
* @flow
7
*/
8
9
-import ReactErrorUtils from 'shared/ReactErrorUtils';
9
+import {rethrowCaughtError} from 'shared/ReactErrorUtils';
10
import invariant from 'shared/invariant';
11
12
import {
@@ -224,7 +224,7 @@ export function runEventsInBatch(
224
'an event queue. Support for this has not yet been implemented.',
225
);
226
// This would be a good time to rethrow if any of the event handlers threw.
227
- ReactErrorUtils.rethrowCaughtError();
227
+ rethrowCaughtError();
228
}
229
230
export function runExtractedEventsInBatch(
packages/events/EventPluginUtils.js
+2
-7
@@ -5,7 +5,7 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import ReactErrorUtils from 'shared/ReactErrorUtils';
8
+import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils';
9
import invariant from 'shared/invariant';
10
import warningWithoutStack from 'shared/warningWithoutStack';
11
@@ -67,12 +67,7 @@ if (__DEV__) {
67
function executeDispatch(event, simulated, listener, inst) {
68
const type = event.type || 'unknown-event';
69
event.currentTarget = getNodeFromInstance(inst);
70
- ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
71
- type,
72
- listener,
73
- undefined,
74
- event,
75
- );
70
+ invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event);
71
event.currentTarget = null;
72
}
73
packages/react-reconciler/src/ReactFiberCommitWork.js
+5
-7
@@ -29,7 +29,11 @@ import {
29
Profiler,
30
PlaceholderComponent,
31
} from 'shared/ReactTypeOfWork';
32
-import ReactErrorUtils from 'shared/ReactErrorUtils';
32
+import {
33
+ invokeGuardedCallback,
34
+ hasCaughtError,
35
+ clearCaughtError,
36
+} from 'shared/ReactErrorUtils';
37
import {
38
NoEffect,
39
ContentReset,
@@ -72,12 +76,6 @@ import {
76
} from './ReactFiberScheduler';
77
import {StrictMode} from './ReactTypeOfMode';
78
75
-const {
76
- invokeGuardedCallback,
77
- hasCaughtError,
78
- clearCaughtError,
79
-} = ReactErrorUtils;
80
-
79
const emptyObject = {};
80
81
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
packages/react-reconciler/src/ReactFiberScheduler.js
+5
-6
@@ -11,7 +11,11 @@ import type {Fiber} from './ReactFiber';
11
import type {FiberRoot, Batch} from './ReactFiberRoot';
12
import type {ExpirationTime} from './ReactFiberExpirationTime';
13
14
-import ReactErrorUtils from 'shared/ReactErrorUtils';
14
+import {
15
+ invokeGuardedCallback,
16
+ hasCaughtError,
17
+ clearCaughtError,
18
+} from 'shared/ReactErrorUtils';
19
import ReactSharedInternals from 'shared/ReactSharedInternals';
20
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
21
import {
@@ -151,11 +155,6 @@ export type Thenable = {
155
};
156
157
const {ReactCurrentOwner} = ReactSharedInternals;
154
-const {
155
- invokeGuardedCallback,
156
- hasCaughtError,
157
- clearCaughtError,
158
-} = ReactErrorUtils;
158
159
let didWarnAboutStateTransition;
160
let didWarnSetStateChildContext;
packages/shared/ReactErrorUtils.js
+105
-96
@@ -8,110 +8,119 @@
8
*/
9
10
import invariant from 'shared/invariant';
11
-import invokeGuardedCallback from './invokeGuardedCallback';
11
+import invokeGuardedCallbackImpl from './invokeGuardedCallbackImpl';
12
13
-const ReactErrorUtils = {
14
- // Used by Fiber to simulate a try-catch.
15
- _caughtError: (null: mixed),
16
- _hasCaughtError: (false: boolean),
13
+// Used by Fiber to simulate a try-catch.
14
+let hasError: boolean = false;
15
+let caughtError: mixed = null;
16
18
- // Used by event system to capture/rethrow the first error.
19
- _rethrowError: (null: mixed),
20
- _hasRethrowError: (false: boolean),
17
+// Used by event system to capture/rethrow the first error.
18
+let hasRethrowError: boolean = false;
19
+let rethrowError: mixed = null;
20
22
- /**
23
- * Call a function while guarding against errors that happens within it.
24
- * Returns an error if it throws, otherwise null.
25
- *
26
- * In production, this is implemented using a try-catch. The reason we don't
27
- * use a try-catch directly is so that we can swap out a different
28
- * implementation in DEV mode.
29
- *
30
- * @param {String} name of the guard to use for logging or debugging
31
- * @param {Function} func The function to invoke
32
- * @param {*} context The context to use when calling the function
33
- * @param {...*} args Arguments for function
34
- */
35
- invokeGuardedCallback: function<A, B, C, D, E, F, Context>(
36
- name: string | null,
37
- func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
38
- context: Context,
39
- a: A,
40
- b: B,
41
- c: C,
42
- d: D,
43
- e: E,
44
- f: F,
45
- ): void {
46
- invokeGuardedCallback.apply(ReactErrorUtils, arguments);
47
- },
48
-
49
- /**
50
- * Same as invokeGuardedCallback, but instead of returning an error, it stores
51
- * it in a global so it can be rethrown by `rethrowCaughtError` later.
52
- * TODO: See if _caughtError and _rethrowError can be unified.
53
- *
54
- * @param {String} name of the guard to use for logging or debugging
55
- * @param {Function} func The function to invoke
56
- * @param {*} context The context to use when calling the function
57
- * @param {...*} args Arguments for function
58
- */
59
- invokeGuardedCallbackAndCatchFirstError: function<A, B, C, D, E, F, Context>(
60
- name: string | null,
61
- func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
62
- context: Context,
63
- a: A,
64
- b: B,
65
- c: C,
66
- d: D,
67
- e: E,
68
- f: F,
69
- ): void {
70
- ReactErrorUtils.invokeGuardedCallback.apply(this, arguments);
71
- if (ReactErrorUtils.hasCaughtError()) {
72
- const error = ReactErrorUtils.clearCaughtError();
73
- if (!ReactErrorUtils._hasRethrowError) {
74
- ReactErrorUtils._hasRethrowError = true;
75
- ReactErrorUtils._rethrowError = error;
76
- }
77
- }
78
- },
79
-
80
- /**
81
- * During execution of guarded functions we will capture the first error which
82
- * we will rethrow to be handled by the top level error handler.
83
- */
84
- rethrowCaughtError: function() {
85
- return rethrowCaughtError.apply(ReactErrorUtils, arguments);
21
+const reporter = {
22
+ onError(error: mixed) {
23
+ hasError = true;
24
+ caughtError = error;
25
},
26
+};
27
88
- hasCaughtError: function() {
89
- return ReactErrorUtils._hasCaughtError;
90
- },
28
+/**
29
+ * Call a function while guarding against errors that happens within it.
30
+ * Returns an error if it throws, otherwise null.
31
+ *
32
+ * In production, this is implemented using a try-catch. The reason we don't
33
+ * use a try-catch directly is so that we can swap out a different
34
+ * implementation in DEV mode.
35
+ *
36
+ * @param {String} name of the guard to use for logging or debugging
37
+ * @param {Function} func The function to invoke
38
+ * @param {*} context The context to use when calling the function
39
+ * @param {...*} args Arguments for function
40
+ */
41
+export function invokeGuardedCallback<A, B, C, D, E, F, Context>(
42
+ name: string | null,
43
+ func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
44
+ context: Context,
45
+ a: A,
46
+ b: B,
47
+ c: C,
48
+ d: D,
49
+ e: E,
50
+ f: F,
51
+): void {
52
+ hasError = false;
53
+ caughtError = null;
54
+ invokeGuardedCallbackImpl.apply(reporter, arguments);
55
+}
56
92
- clearCaughtError: function() {
93
- if (ReactErrorUtils._hasCaughtError) {
94
- const error = ReactErrorUtils._caughtError;
95
- ReactErrorUtils._caughtError = null;
96
- ReactErrorUtils._hasCaughtError = false;
97
- return error;
98
- } else {
99
- invariant(
100
- false,
101
- 'clearCaughtError was called but no error was captured. This error ' +
102
- 'is likely caused by a bug in React. Please file an issue.',
103
- );
57
+/**
58
+ * Same as invokeGuardedCallback, but instead of returning an error, it stores
59
+ * it in a global so it can be rethrown by `rethrowCaughtError` later.
60
+ * TODO: See if caughtError and rethrowError can be unified.
61
+ *
62
+ * @param {String} name of the guard to use for logging or debugging
63
+ * @param {Function} func The function to invoke
64
+ * @param {*} context The context to use when calling the function
65
+ * @param {...*} args Arguments for function
66
+ */
67
+export function invokeGuardedCallbackAndCatchFirstError<
68
+ A,
69
+ B,
70
+ C,
71
+ D,
72
+ E,
73
+ F,
74
+ Context,
75
+>(
76
+ name: string | null,
77
+ func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,
78
+ context: Context,
79
+ a: A,
80
+ b: B,
81
+ c: C,
82
+ d: D,
83
+ e: E,
84
+ f: F,
85
+): void {
86
+ invokeGuardedCallback.apply(this, arguments);
87
+ if (hasError) {
88
+ const error = clearCaughtError();
89
+ if (!hasRethrowError) {
90
+ hasRethrowError = true;
91
+ rethrowError = error;
92
}
105
- },
106
-};
93
+ }
94
+}
95
108
-let rethrowCaughtError = function() {
109
- if (ReactErrorUtils._hasRethrowError) {
110
- const error = ReactErrorUtils._rethrowError;
111
- ReactErrorUtils._rethrowError = null;
112
- ReactErrorUtils._hasRethrowError = false;
96
+/**
97
+ * During execution of guarded functions we will capture the first error which
98
+ * we will rethrow to be handled by the top level error handler.
99
+ */
100
+export function rethrowCaughtError() {
101
+ if (hasRethrowError) {
102
+ const error = rethrowError;
103
+ hasRethrowError = false;
104
+ rethrowError = null;
105
throw error;
106
}
115
-};
107
+}
108
117
-export default ReactErrorUtils;
109
+export function hasCaughtError() {
110
+ return hasError;
111
+}
112
+
113
+export function clearCaughtError() {
114
+ if (hasError) {
115
+ const error = caughtError;
116
+ hasError = false;
117
+ caughtError = null;
118
+ return error;
119
+ } else {
120
+ invariant(
121
+ false,
122
+ 'clearCaughtError was called but no error was captured. This error ' +
123
+ 'is likely caused by a bug in React. Please file an issue.',
124
+ );
125
+ }
126
+}
packages/shared/__tests__/ReactErrorUtils-test.internal.js
+7
-8
@@ -14,7 +14,7 @@ let ReactErrorUtils;
14
describe('ReactErrorUtils', () => {
15
beforeEach(() => {
16
// TODO: can we express this test with only public API?
17
- ReactErrorUtils = require('shared/ReactErrorUtils').default;
17
+ ReactErrorUtils = require('shared/ReactErrorUtils');
18
});
19
20
it(`it should rethrow caught errors`, () => {
@@ -128,9 +128,9 @@ describe('ReactErrorUtils', () => {
128
});
129
130
it('handles nested errors in separate renderers', () => {
131
- const ReactErrorUtils1 = require('shared/ReactErrorUtils').default;
131
+ const ReactErrorUtils1 = require('shared/ReactErrorUtils');
132
jest.resetModules();
133
- const ReactErrorUtils2 = require('shared/ReactErrorUtils').default;
133
+ const ReactErrorUtils2 = require('shared/ReactErrorUtils');
134
expect(ReactErrorUtils1).not.toEqual(ReactErrorUtils2);
135
136
let ops = [];
@@ -178,19 +178,18 @@ describe('ReactErrorUtils', () => {
178
const ops = [];
179
jest.resetModules();
180
jest.mock(
181
- 'shared/invokeGuardedCallback',
181
+ 'shared/invokeGuardedCallbackImpl',
182
() =>
183
function invokeGuardedCallback(name, func, context, a) {
184
ops.push(a);
185
try {
186
func.call(context, a);
187
} catch (error) {
188
- this._hasCaughtError = true;
189
- this._caughtError = error;
188
+ this.onError(error);
189
}
190
},
191
);
193
- ReactErrorUtils = require('shared/ReactErrorUtils').default;
192
+ ReactErrorUtils = require('shared/ReactErrorUtils');
193
194
try {
195
const err = new Error('foo');
@@ -206,7 +205,7 @@ describe('ReactErrorUtils', () => {
205
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
206
expect(ops).toEqual(['somearg']);
207
} finally {
209
- jest.unmock('shared/invokeGuardedCallback');
208
+ jest.unmock('shared/invokeGuardedCallbackImpl');
209
}
210
});
211
});
packages/shared/forks/invokeGuardedCallback.www.js
deleted
-17
@@ -1,17 +0,0 @@
1
-/**
2
- * Copyright (c) 2013-present, Facebook, Inc.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-import invariant from 'shared/invariant';
9
-
10
-const invokeGuardedCallback = require('ReactFbErrorUtils')
11
- .invokeGuardedCallback;
12
-invariant(
13
- typeof invokeGuardedCallback === 'function',
14
- 'Expected ReactFbErrorUtils.invokeGuardedCallback to be a function.',
15
-);
16
-
17
-export default invokeGuardedCallback;
packages/shared/forks/invokeGuardedCallbackImpl.www.js
new
+43
@@ -0,0 +1,43 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import invariant from 'shared/invariant';
9
+
10
+// Provided by www
11
+const ReactFbErrorUtils = require('ReactFbErrorUtils');
12
+invariant(
13
+ typeof ReactFbErrorUtils.invokeGuardedCallback === 'function',
14
+ 'Expected ReactFbErrorUtils.invokeGuardedCallback to be a function.',
15
+);
16
+
17
+// This object will be mutated by the www version.
18
+let result = {
19
+ _hasCaughtError: false,
20
+ _caughtError: null,
21
+};
22
+
23
+let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(
24
+ name: string | null,
25
+ func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
26
+ context: Context,
27
+ a: A,
28
+ b: B,
29
+ c: C,
30
+ d: D,
31
+ e: E,
32
+ f: F,
33
+) {
34
+ // The www version doesn't take the `onError` callback on context.
35
+ // Instead, it writes the result to fields on context.
36
+ // TODO: change the www function API so we can re-export it directly.
37
+ ReactFbErrorUtils.invokeGuardedCallback.apply(result, arguments);
38
+ if (result._hasCaughtError) {
39
+ this.onError(result._caughtError);
40
+ }
41
+};
42
+
43
+export default invokeGuardedCallbackImpl;
packages/shared/invokeGuardedCallbackImpl.js
renamed
+8
-15
@@ -9,7 +9,7 @@
9
10
import invariant from 'shared/invariant';
11
12
-let invokeGuardedCallback = function<A, B, C, D, E, F, Context>(
12
+let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(
13
name: string | null,
14
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
15
context: Context,
@@ -20,14 +20,11 @@ let invokeGuardedCallback = function<A, B, C, D, E, F, Context>(
20
e: E,
21
f: F,
22
) {
23
- this._hasCaughtError = false;
24
- this._caughtError = null;
23
const funcArgs = Array.prototype.slice.call(arguments, 3);
24
try {
25
func.apply(context, funcArgs);
26
} catch (error) {
29
- this._caughtError = error;
30
- this._hasCaughtError = true;
27
+ this.onError(error);
28
}
29
};
30
@@ -143,7 +140,7 @@ if (__DEV__) {
140
let didSetError = false;
141
let isCrossOriginError = false;
142
146
- function onError(event) {
143
+ function handleWindowError(event) {
144
error = event.error;
145
didSetError = true;
146
if (error === null && event.colno === 0 && event.lineno === 0) {
@@ -167,7 +164,7 @@ if (__DEV__) {
164
const evtType = `react-${name ? name : 'invokeguardedcallback'}`;
165
166
// Attach our event handlers
170
- window.addEventListener('error', onError);
167
+ window.addEventListener('error', handleWindowError);
168
fakeNode.addEventListener(evtType, callCallback, false);
169
170
// Synchronously dispatch our fake event. If the user-provided function
@@ -195,19 +192,15 @@ if (__DEV__) {
192
'See https://fb.me/react-crossorigin-error for more information.',
193
);
194
}
198
- this._hasCaughtError = true;
199
- this._caughtError = error;
200
- } else {
201
- this._hasCaughtError = false;
202
- this._caughtError = null;
195
+ this.onError(error);
196
}
197
198
// Remove our event listeners
206
- window.removeEventListener('error', onError);
199
+ window.removeEventListener('error', handleWindowError);
200
};
201
209
- invokeGuardedCallback = invokeGuardedCallbackDev;
202
+ invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
203
}
204
}
205
213
-export default invokeGuardedCallback;
206
+export default invokeGuardedCallbackImpl;
scripts/rollup/forks.js
+2
-2
@@ -175,12 +175,12 @@ const forks = Object.freeze({
175
},
176
177
// Different wrapping/reporting for caught errors.
178
- 'shared/invokeGuardedCallback': (bundleType, entry) => {
178
+ 'shared/invokeGuardedCallbackImpl': (bundleType, entry) => {
179
switch (bundleType) {
180
case FB_WWW_DEV:
181
case FB_WWW_PROD:
182
case FB_WWW_PROFILING:
183
- return 'shared/forks/invokeGuardedCallback.www.js';
183
+ return 'shared/forks/invokeGuardedCallbackImpl.www.js';
184
default:
185
return null;
186
}