前端常见各种错误捕抓方式

185 阅读3分钟

1.try/catch

只能捕获代码常规的运行错误,语法错误和异步错误不能捕获到

// 示例1:常规运行时错误,可以捕获 ✅
 try {
   let a = undefined;
   if (a.length) {
     console.log('111');
   }
 } catch (e) {
   console.log('捕获到异常:', e);
}

// 示例2:语法错误,不能捕获 ❌  
try {
  const notdefined,
} catch(e) {
  console.log('捕获不到异常:', 'Uncaught SyntaxError');
}
  
// 示例3:异步错误,不能捕获 ❌
try {
  setTimeout(() => {
    console.log(notdefined);
  }, 0)
} catch(e) {
  console.log('捕获不到异常:', 'Uncaught ReferenceError');
}

2.window.onerror

window.onerror 可以捕获常规错误、异步错误,但不能捕获资源错误

window.onerror = function(message, source, lineno, colno, error) {
  console.log("捕获到的错误信息是:", message, source, lineno, colno, error);
};

// 示例1:常规运行时错误,可以捕获 ✅
console.log(notdefined);

// 示例2:语法错误,不能捕获 ❌
const notdefined;

// 示例3:异步错误,可以捕获 ✅
setTimeout(() => {
  console.log(notdefined);
}, 0);

// 示例4:资源错误,不能捕获 ❌
let script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://www.test.com/index.js";
document.body.appendChild(script);

3.Promise错误

Promise中抛出的错误,无法被 window.onerror、try/catch、 error 事件捕获到,可通过 unhandledrejection 事件来处理

try {
  new Promise((resolve, reject) => {
    JSON.parse("");
    resolve();
  });
} catch (err) {
  // try/catch 不能捕获Promise中错误 ❌
  console.error("in try catch", err);
}

// error事件 不能捕获Promise中错误 ❌
window.addEventListener(
  "error",
  error => {
    console.log("捕获到异常:", error);
  },
  true
);

// window.onerror 不能捕获Promise中错误 ❌
window.onerror = function(message, source, lineno, colno, error) {
  console.log("捕获到异常:", { message, source, lineno, colno, error });
};

// unhandledrejection 可以捕获Promise中的错误 ✅
window.addEventListener("unhandledrejection", function(e) {
  console.log("捕获到异常", e);
  // preventDefault阻止传播,不会在控制台打印
  e.preventDefault();
});

4.window.addEventListener

当静态资源加载失败时,会触发 error 事件, 此时 window.onerror 不能捕获到

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
</head>
<script>
  window.addEventListener('error', (error) => {
    console.log('捕获到异常:', error);
  }, true)
</script>

<!-- 图片、script、css加载错误,都能被捕获 ✅ -->
<img src="https://test.cn/×××.png">
<script src="https://test.cn/×××.js"></script>
<link href="https://test.cn/×××.css" rel="stylesheet" />

<script>
  // new Image错误,不能捕获 ❌
  // new Image运用的比较少,可以自己单独处理
  new Image().src = 'https://test.cn/×××.png'
</script>
</html>

5.Vue错误

Vue项目中,window.onerror 和 error 事件不能捕获到常规的代码错误

//异常代码:
export default {
  created() {
    let a = null;
    if(a.length > 1) {
        // ...
    }
  }
};
//main.js 添加捕获代码:
window.addEventListener('error', (error) => {
  console.log('error', error);
});
window.onerror = function (msg, url, line, col, error) {
  console.log('onerror', msg, url, line, col, error);
};
//控制台会报错,但是 window.onerror 和 error 不能捕获到
//vue 通过 `Vue.config.errorHander` 来捕获异常:
Vue.config.errorHandler = (err, vm, info) => { console.log('进来啦~', err); }

6.React 错误

从 react16 开始,官方提供了 ErrorBoundary 错误边界的功能,被该组件包裹的子组件,render 函数报错时会触发离当前组件最近父组件的ErrorBoundary 生产环境,一旦被 ErrorBoundary 捕获的错误,也不会触发全局的 window.onerror 和 error 事件

// 父组件代码
import React from 'react';
import Child from './Child.js';

// window.onerror 不能捕获render函数的错误 ❌
window.onerror = function (err, msg, c, l) {
  console.log('err', err, msg);
};

// error 不能render函数的错误 ❌
window.addEventListener( 'error', (error) => {
    console.log('捕获到异常:', error);
  },true
);

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }
  componentDidCatch(error, errorInfo) {
    // componentDidCatch 可以捕获render函数的错误 
    console.log(error, errorInfo)
    
    // 同样可以将错误日志上报给服务器
    reportError(error, errorInfo);
  }
  render() {
    if (this.state.hasError) {
      // 自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

function Parent() {
  return (
    <div>
      父组件
      <ErrorBoundary>
        <Child />
      </ErrorBoundary>
    </div>
  );
}

export default Parent;
// 子组件代码
// 子组件 渲染出错
function Child() {
  let list = {};
  return (
    <div>
      子组件
      {list.map((item, key) => (
        <span key={key}>{item}</span>
      ))}
    </div>
  );
}
export default Child;
// 同vue项目的处理类似,react项目中,可以在 componentDidCatch 中将捕获的错误上报
componentDidCatch(error, errorInfo) {
  // handleError方法用来处理错误并上报
  handleError(err);
}

7.跨域问题

如果当前页面中,引入了其他域名的JS资源,如果资源出现错误,error 事件只会监测到一个 script error 的异常。 是由于浏览器基于安全考虑,故意隐藏了其它域JS文件抛出的具体错误信息,这样可以有效避免敏感信息无意中被第三方(不受控制的)脚本捕获到,因此,浏览器只允许同域下的脚本捕获具体的错误信息

window.addEventListener("error", error => { 
  console.log("捕获到异常:", error);
}, true );

// 当前页面加载其他域的资源,如https://www.test.com/index.js
<script src="https://www.test.com/index.js"></script>

// 加载的https://www.test.com/index.js的代码
function fn() {
  JSON.parse("");
}
fn();

// 解决方法:
// 前端script加crossorigin,后端配置 Access-Control-Allow-Origin 添加 crossorigin 后可以捕获到完整的报错信息:
<script src="https://www.test.com/index.js" crossorigin></script>