react取消捕获_react中的错误处理

解决的两个问题

收集错误

自定义事件实现try...catch错误捕获功能同时增加Pause on exceptions功能

react\packages\shared\ReactErrorUtils.js

用于存储错误以及是否发生错误的状态

// Used by Fiber to simulate a try-catch.

let hasError: boolean = false;

let caughtError: mixed = null;

复制代码

需要重新抛出的错误以及重新抛出的状态

// Used by event system to capture/rethrow the first error.

let hasRethrowError: boolean = false;

let rethrowError: mixed = null;

复制代码

reporter对象上的onError方法提供一个借口改变错误的状态以及存储捕获到的错误对象

const reporter = {

onError(error: mixed) {

hasError = true;

caughtError = error;

},

};

复制代码

错误系统入口函数,用于捕获第一个错误

对invokeGuardedCallback的封装,当执行完invokeGuardedCallback之后,通过hasError判断是否发生了错误,并调用clearCaughtError返回错误对象,并清空hasError、caughtError。然后将返回的错误存储到rethrowError,并改变hasRethrowError的状态。

function invokeGuardedCallbackAndCatchFirstError(name,func,context,a,b,c,d,e,f) {

invokeGuardedCallback.apply(this, arguments);

if (hasError) {

const error = clearCaughtError();

if (!hasRethrowError) {

hasRethrowError = true;

rethrowError = error;

}

}

}

export function clearCaughtError() {

if (hasError) {

const error = caughtError;

hasError = false;

caughtError = null;

return error;

} else {

invariant(

false,

'clearCaughtError was called but no error was captured. This error ' +

'is likely caused by a bug in React. Please file an issue.',

);

}

}

复制代码

调用传入函数func的准备

对invokeGuardedCallbackImpl的封装,利用apply将this指向reporter,用于当invokeGuardedCallbackImpl函数发生错误的时候,调用reporter上的接口onError,将错误对象存储到最外层ReactErrorUtils.js文件模块的hasError、caughtError变量上。

export function invokeGuardedCallback(name,func,context,a,b,c,d,e,f) {

hasError = false;

caughtError = null;

invokeGuardedCallbackImpl.apply(reporter, arguments);

}

复制代码

直接执行的func的invokeGuardedCallbackImpl

invokeGuardedCallbackImpl在不同的环境下有不同的实现:

在生产环境下利用try ... catch捕获错误,并存储错误到hasError、caughtError上。

在开发环境下利用自定义事件模拟了try ... catch功能,同时具备在开发工具中在所有异常发生的地方自动断点的功能。如果用try ... catch,那么try块语句中出现的错误不会断点暂停,因为这个异常被catch捕获了,除非在chrome下将Pause on exceptions打开。

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.

下面主要分析invokeGuardedCallbackImpl

react\packages\shared\invokeGuardedCallbackImpl.js

生产环境:利用try-catch实现错误捕获

let invokeGuardedCallbackImpl = function(name,func,context,a,b,c,d,e,f){

const funcArgs = Array.prototype.slice.call(arguments, 3);

try {

func.apply(context, funcArgs);

} catch (error) {

this.onError(error);

}

};

复制代码

将arguments转成数组,然后执行传入的函数func,执行上下文指定为传入的context。如果func执行过程中发生错误,抛出的错误被捕获并通过this.onError传入最外层的变量上。这里的this指向的是reporter。

开发环境:模拟try-catch

const fakeNode = document.createElement('react');

const invokeGuardedCallbackDev = function(name,func,context,a,b,c,d,e,f){

let windowEvent = window.event;

const windowEventDescriptor = Object.getOwnPropertyDescriptor(

window,

'event',

);

const funcArgs = Array.prototype.slice.call(arguments, 3);

/**

* 给window添加error事件监听函数,未捕获的错误都会经过这里

*/

let error;

let didSetError = false;

let isCrossOriginError = false;

function handleWindowError(event) {

error = event.error;

didSetError = true;

if (error === null && event.colno === 0 && event.lineno === 0) {

// 当加载自不同域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,而代之简单的"Script error."

// event:

// message:错误信息(字符串)。可用于HTML οnerrοr=""处理程序中的event。

// source:发生错误的脚本URL(字符串)

// lineno:发生错误的行号(数字)

// colno:发生错误的列号(数字)

// error:Error对象(对象)

isCrossOriginError = true;

}

if (event.defaultPrevented) {

if (error != null && typeof error === 'object') {

try {

error._suppressLogging = true;

} catch (inner) {

// Ignore.

}

}

}

}

window.addEventListener('error', handleWindowError);

/**

* 利用createEvent创建自定义事件event,并利用initEvent指定名称,然后给DOM fakeNode添加evtType事件监听函数

*/

let didError = true;

const evtType = `react-${name ? name : 'invokeguardedcallback'}`;

const evt = document.createEvent('Event');

evt.initEvent(evtType, false/*false表示阻止该事件向上冒泡*/, false/*false表示该事件默认动作不可取消*/);

function callCallback() {

fakeNode.removeEventListener(evtType, callCallback, false);

if (

typeof window.event !== 'undefined' &&

window.hasOwnProperty('event')

) {

window.event = windowEvent;

}

func.apply(context, funcArgs);

didError = false;

}

fakeNode.addEventListener(evtType, callCallback, false);

/**

* 手动触发fakeNode上的evt事件,此时会先执行传入的func函数,如果发生错误会触发window上的error事件

*/

fakeNode.dispatchEvent(evt);

/**

* 还原window.event

*/

if (windowEventDescriptor) {

Object.defineProperty(window, 'event', windowEventDescriptor);

}

/**

* 调用onError记录error

*/

if (didError) {

if (!didSetError) {

error = new Error(

'An error was thrown inside one of your components'

);

} else if (isCrossOriginError) {

error = new Error(

"A cross-origin error was thrown. React doesn't have access to ",

);

}

this.onError(error);

}

// Remove our event listeners

window.removeEventListener('error', handleWindowError);

};

invokeGuardedCallbackImpl = invokeGuardedCallbackDev;

复制代码

chrome下Pause on exceptions功能

操作: f12 -> Source Tab -> 点击 Pause on exceptions 暂停图标 -> 图标变成蓝色,表明启用了在未捕获到的异常出现的时候断点的功能。

勾选Pause On Caught Exceptions, 能够在捕获到异常的情况下也断点。

try{

throw'a exception';

}catch(e){

console.log(e);

}

复制代码

上面 try 里面的代码会遇到异常,但是后面的 catch 代码能够捕获该异常。如果是所有异常都中断(勾选了Pause On Caught Exceptions),那么代码执行到会产生异常的 throw 语句时就会自动中断;而如果是仅遇到未捕获异常才中断,那么这里就不会中断。一般我们会更关心遇到未捕获异常的情况。

你可能感兴趣的:(react取消捕获)