@samitouri / QOS-React / commits / 83e446e1d8

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 }