1 )概述
invokeGuardedCallback
这个方法2 )概述
定位到 packages/shared/ReactErrorUtils.js#L41
进入 invokeGuardedCallback
const reporter = {
onError(error: mixed) {
hasError = true;
caughtError = error;
},
};
/**
* Call a function while guarding against errors that happens within it.
* Returns an error if it throws, otherwise null.
*
* In production, this is implemented using a try-catch. The reason we don't
* use a try-catch directly is so that we can swap out a different
* implementation in DEV mode.
*
* @param {String} name of the guard to use for logging or debugging
* @param {Function} func The function to invoke
* @param {*} context The context to use when calling the function
* @param {...*} args Arguments for function
*/
export function invokeGuardedCallback<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
): void {
hasError = false;
caughtError = null;
invokeGuardedCallbackImpl.apply(reporter, arguments);
}
invokeGuardedCallbackImpl.apply(reporter, arguments);
reporter
,是定义的一个全局对象hasError
和 caughtError
invokeGuardedCallbackImpl
// https://github.com/facebook/react/blob/v16.6.3/packages/shared/invokeGuardedCallbackImpl.js
let invokeGuardedCallbackImpl = function<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
) {
const funcArgs = Array.prototype.slice.call(arguments, 3);
try {
func.apply(context, funcArgs);
} catch (error) {
this.onError(error);
}
};
if (__DEV__) {
// In DEV mode, we swap out invokeGuardedCallback for a special version
// that plays more nicely with the browser's DevTools. The idea is to preserve
// "Pause on exceptions" behavior. Because React wraps all user-provided
// functions in invokeGuardedCallback, and the production version of
// invokeGuardedCallback uses a try-catch, all user exceptions are treated
// like caught exceptions, and the DevTools won't pause unless the developer
// takes the extra step of enabling pause on caught exceptions. This is
// untintuitive, though, because even though React has caught the error, from
// the developer's perspective, the error is uncaught.
//
// To preserve the expected "Pause on exceptions" behavior, we don't use a
// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake
// DOM node, and call the user-provided callback from inside an event handler
// for that fake event. If the callback throws, the error is "captured" using
// a global event handler. But because the error happens in a different
// event loop context, it does not interrupt the normal program flow.
// Effectively, this gives us try-catch behavior without actually using
// try-catch. Neat!
// Check that the browser supports the APIs we need to implement our special
// DEV version of invokeGuardedCallback
if (
typeof window !== 'undefined' &&
typeof window.dispatchEvent === 'function' &&
typeof document !== 'undefined' &&
typeof document.createEvent === 'function'
) {
const fakeNode = document.createElement('react');
const invokeGuardedCallbackDev = function<A, B, C, D, E, F, Context>(
name: string | null,
func: (a: A, b: B, c: C, d: D, e: E, f: F) => mixed,
context: Context,
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
) {
// If document doesn't exist we know for sure we will crash in this method
// when we call document.createEvent(). However this can cause confusing
// errors: https://github.com/facebookincubator/create-react-app/issues/3482
// So we preemptively throw with a better message instead.
// 首先会有一大串长的提醒,说明当前这个环境是不支持的
invariant(
typeof document !== 'undefined',
'The `document` global was defined when React was initialized, but is not ' +
'defined anymore. This can happen in a test environment if a component ' +
'schedules an update from an asynchronous callback, but the test has already ' +
'finished running. To solve this, you can either unmount the component at ' +
'the end of your test (and ensure that any asynchronous operations get ' +
'canceled in `componentWillUnmount`), or you can change the test itself ' +
'to be asynchronous.',
);
// 它创建了一个 event 对象
const evt = document.createEvent('Event');
// Keeps track of whether the user-provided callback threw an error. We
// set this to true at the beginning, then set it to false right after
// calling the function. If the function errors, `didError` will never be
// set to false. This strategy works even if the browser is flaky and
// fails to call our global error handler, because it doesn't rely on
// the error event at all.
// 声明了 didError 为true
let didError = true;
// Keeps track of the value of window.event so that we can reset it
// during the callback to let user code access window.event in the
// browsers that support it.
let windowEvent = window.event;
// Keeps track of the descriptor of window.event to restore it after event
// dispatching: https://github.com/facebook/react/issues/13688
// 来获取 window.event 的对象描述符
const windowEventDescriptor = Object.getOwnPropertyDescriptor(
window,
'event',
);
// Create an event handler for our fake event. We will synchronously
// dispatch our fake event using `dispatchEvent`. Inside the handler, we
// call the user-provided callback.
// 把前面3个参数给它拿出来,剩下的都是作为它的arguments
const funcArgs = Array.prototype.slice.call(arguments, 3);
// 它声明了一个叫做callcallback的一个方法。
function callCallback() {
// We immediately remove the callback from event listeners so that
// nested `invokeGuardedCallback` calls do not clash. Otherwise, a
// nested call would trigger the fake event handlers of any call higher
// in the stack.
fakeNode.removeEventListener(evtType, callCallback, false); // 移除事件监听
// We check for window.hasOwnProperty('event') to prevent the
// window.event assignment in both IE <= 10 as they throw an error
// "Member not found" in strict mode, and in Firefox which does not
// support window.event.
// 对 window.event 进行兼容处理
if (
typeof window.event !== 'undefined' &&
window.hasOwnProperty('event')
) {
window.event = windowEvent;
}
// 使用 context 调用 func 传入 funcArgs 后续的参数
// 注意,在 dev 时,这个 context 可能是 null, 可以向上追溯
func.apply(context, funcArgs);
didError = false;
}
// Create a global error event handler. We use this to capture the value
// that was thrown. It's possible that this error handler will fire more
// than once; for example, if non-React code also calls `dispatchEvent`
// and a handler for that event throws. We should be resilient to most of
// those cases. Even if our error event handler fires more than once, the
// last error event is always used. If the callback actually does error,
// we know that the last error event is the correct one, because it's not
// possible for anything else to have happened in between our callback
// erroring and the code that follows the `dispatchEvent` call below. If
// the callback doesn't error, but the error event was fired, we know to
// ignore it because `didError` will be false, as described above.
// 声明一堆和 error 相关的变量
let error;
// Use this to track whether the error event is ever called.
let didSetError = false;
let isCrossOriginError = false;
function handleWindowError(event) {
error = event.error;
didSetError = true;
// 这种情况下是 跨域错误
if (error === null && event.colno === 0 && event.lineno === 0) {
isCrossOriginError = true;
}
// 别的地方,有去处理 window.onerror,这里被阻塞了
if (event.defaultPrevented) {
// Some other error handler has prevented default.
// Browsers silence the error report if this happens.
// We'll remember this to later decide whether to log it or not.
if (error != null && typeof error === 'object') {
try {
error._suppressLogging = true; // 设置当前标识
} catch (inner) {
// Ignore.
}
}
}
}
// Create a fake event type.
const evtType = `react-${name ? name : 'invokeguardedcallback'}`;
// Attach our event handlers
// 注册两个事件
window.addEventListener('error', handleWindowError);
fakeNode.addEventListener(evtType, callCallback, false);
// Synchronously dispatch our fake event. If the user-provided function
// errors, it will trigger our global error handler.
// 初始化事件,并立即触发 dispatchEvent
evt.initEvent(evtType, false, false);
fakeNode.dispatchEvent(evt); // 触发完,会立即调用 callback
if (windowEventDescriptor) {
Object.defineProperty(window, 'event', windowEventDescriptor);
}
if (didError) {
// 出错了,但是 handleWindowError 没有被调用的处理
if (!didSetError) {
// The callback errored, but the error event never fired.
error = new Error(
'An error was thrown inside one of your components, but React ' +
"doesn't know what it was. This is likely due to browser " +
'flakiness. React does its best to preserve the "Pause on ' +
'exceptions" behavior of the DevTools, which requires some ' +
"DEV-mode only tricks. It's possible that these don't work in " +
'your browser. Try triggering the error in production mode, ' +
'or switching to a modern browser. If you suspect that this is ' +
'actually an issue with React, please file an issue.',
);
} else if (isCrossOriginError) {
error = new Error(
"A cross-origin error was thrown. React doesn't have access to " +
'the actual error object in development. ' +
'See https://fb.me/react-crossorigin-error for more information.',
);
}
this.onError(error);
}
// Remove our event listeners
// 最终移除事件监听
window.removeEventListener('error', handleWindowError);
};
invokeGuardedCallbackImpl = invokeGuardedCallbackDev;
}
}
handleWindowError
中,
callCallback
方法
func
方法didError = false
就不会被执行handleWindowError
这个方法,里面的变量也会发生变化if (didError) { if (!didSetError) {} else if(isCrossOriginError) }
Pause on exceptions
功能Pause on caught exceptions