admin 管理员组

文章数量: 1184232

前端老鸟血泪史:搞不定报错?这套错误处理策略让你少加三天班!

开篇先唠两句

别划走,我知道你刚被控制台那一堆红字搞崩了心态。那种看着满屏飘红的绝望,我懂——上周三凌晨两点,我盯着生产环境那个 Cannot read properties of undefined 的错误,手边的咖啡已经凉透,脑子里只有一个念头:这破代码到底是谁写的?哦,是三个月前的我自己。

咱们今天不整那些虚头八脑的理论,就聊聊怎么让那些该死的 Uncaught TypeError 闭嘴,顺便让你的代码健壮得像穿了防弹衣。说实话,错误处理这事儿吧,新手觉得麻烦,老鸟觉得保命。我见过太多项目,功能做得花里胡哨,一报错直接白屏,用户体验瞬间归零,产品经理的脸色比控制台还黑。

所以啊,这篇文章就是把我这些年踩过的坑、流过的泪、加过的班,统统打包成一份"防猝死指南"。你照着做,不一定能让你代码零 bug,但至少能让你睡个安稳觉。

到底啥是前端错误处理

简单说就是给代码买个保险。浏览器不会惯着你,用户更不会。一旦脚本报错,页面白屏或者按钮点了没反应,产品经理能顺着网线过来打你——这话真不是夸张,我亲眼见过隔壁组的老哥因为线上白屏十分钟,被拉去开了三小时的复盘会。

前端这地方,出错的姿势简直千奇百怪。你以为只是自己写的业务逻辑会崩?太天真了。网络抖一下,接口返回个 null ,用户手贱按了 F12 删掉某个 DOM 元素,甚至浏览器插件瞎注入脚本,都能让你的页面当场去世。所以咱们得把全局错误、资源加载失败、异步 Promise rejection 这些"定时炸弹"都认全了,别等炸了才想起来拆弹。

先说说全局错误。浏览器提供了一个 window.onerror 这个老古董,虽然 API 设计得有点反人类,但确实是最后一道防线。然后是那些 Promise 里没 catch 住的 rejection,现在满世界都是 async/await,一个 await 忘了包 try-catch,控制台就给你飘红。还有资源加载失败——图片挂了、CSS 丢了、JS 文件 404 了,这些都不会冒泡到 window.onerror,得用 window.addEventListener('error', ...) 专门监听。

最阴间的是那种第三方脚本搞出来的错。你接了个统计 SDK,或者埋了个广告位,他们代码一崩,你的页面跟着陪葬,找谁说理去?所以错误处理不是可选项,是刚需,是底线,是让你能安心下班的护身符。

扒开底层看看怎么抓错

光知道有错不行,得知道怎么逮住它。这节咱们把几个核心 API 和机制掰开了揉碎了讲,代码管够,注释写满,复制粘贴就能用。

window.onerror 这老伙计

说实话, window.onerror 的 API 设计得挺离谱的,参数顺序跟别的 Web API 完全不是一个路数,但谁让人家辈分高呢?这玩意儿能捕获大部分同步错误,是全局监控的基石。

// 基础版:先能跑起来再说
window.onerror=function(message, source, lineno, colno, error){
  console.log('抓到一个错误:',{
    message,// 错误信息字符串,比如 "Uncaught TypeError: xxx is not a function"
    source,// 出错的文件 URL
    lineno,// 行号
    colno,// 列号
    error         // 错误对象,里面堆栈信息最值钱});// 返回 true 可以阻止错误冒泡到控制台,但生产环境建议别瞎阻止,不然调试更难returnfalse;};// 进阶版:带点实际功能的
window.onerror=function(msg, url, line, col, error){// 过滤掉一些无关痛痒的扩展插件错误,不然日志里全是垃圾if(url && url.includes('chrome-extension')){returntrue;// 这种错误直接吞了,爱谁谁}// 构造错误报告对象const errorInfo ={type:'javascript',message: msg,filename: url,position:`${line}:${col}`,stack: error?.stack ||'no stack',// 顺便捞点环境信息,排查问题时候救命用userAgent: navigator.userAgent,timestamp:newDate().toISOString(),// 如果用了性能监控,可以把当前页面加载时间也带上timing: performance?.timing ?{domReady: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart
    }:null};// 发送到监控系统,这里用 console 模拟一下
  console.error('[全局错误捕获]', errorInfo);// 上报到服务器reportError(errorInfo).catch(e=>{// 上报失败也得记一下,别死循环
    console.warn('错误上报失败:', e);});returnfalse;// 让错误继续抛出来,方便开发时看到};// 模拟上报函数asyncfunctionreportError(info){// 实际项目中这里换成你的上报接口// 比如 Sentry.captureException 或者自研的日志服务awaitfetch('/api/log/error',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(info),// 错误上报要用 keepalive,防止页面关闭时请求被掐断keepalive:true});}

注意几个坑:第一, window.onerror 捕获不到 Promise 的错误,那是另一个故事;第二,跨域的脚本错误,如果不配 crossorigin 属性,你拿到的信息只有 Script error ,跟没抓一样;第三,返回值 true 能阻止默认处理(就是控制台不飘红了),但生产环境建议别乱用,不然真出问题了你连调试信息都看不到。

unhandledrejection 专门治各种不服

现在谁还不用 Promise 或者 async/await?这玩意儿用爽了,错误处理也更容易翻车。一个 async 函数里 await 了七八个接口,中间哪个抛异常没包住,控制台就给你来个 Uncaught (in promise) Error

// 基础监听
window.addEventListener('unhandledrejection',function(event){// event.reason 就是 rejected 的值,可能是 Error 对象,也可能是个字符串,甚至是个 undefined
  console.error('有 Promise 没被 catch:', event.reason);// 阻止默认行为(控制台飘红),看情况决定要不要阻止// event.preventDefault();});// 生产环境实用版本
window.addEventListener('unhandledrejection',function(event){const reason = event.reason;// 构造错误信息,得兼容各种奇奇怪怪的 rejection 值let message ='Unhandled Promise Rejection';let stack ='';if(reason instanceofError){
    message = reason.message;
    stack = reason.stack;}elseif(typeof reason ==='string'){
    message = reason;}else{// 有些人喜欢 reject 个对象,比如 reject({code: 500, msg: 'error'})try{
      message =JSON.stringify(reason);}catch(e){
      message =String(reason);}}const errorInfo ={type:'unhandledrejection',message: message,stack: stack,// 如果能拿到触发错误的源头,记下来source: event.target?.src ||'unknown',timestamp:newDate().toISOString()};
  
  console.error('[未捕获的 Promise 错误]', errorInfo);reportError(errorInfo);// 这里建议不要 preventDefault,让错误继续暴露,开发时能及时发现});// 还有个对应的 handledrejection,用来监听那些被补 catch 的错误// 这个一般用得少,主要是做统计或者清理工作
window.addEventListener('rejectionhandled',function(event){
  console.log('刚刚那个错误被 catch 住了:', event.reason);});

这里有个血泪教训:很多开发者喜欢在 async 函数里一把梭,所有 await 都不包 try-catch,指望全局监听兜底。这想法很危险,因为 unhandledrejection 触发的时候,你的代码执行上下文可能已经丢了,想做错误恢复或者重试都没戏。所以全局监听只能是最后一道防线,不能当主力用。

try-catch 的手动挡模式

有些逻辑你得自己包起来,特别是那些第三方库调用或者复杂的业务计算。别指望浏览器全包圆,它没那个义务。

// 基础用法,地球人都会try{const result =JSON.parse(userInput);processData(result);}catch(error){
  console.error('JSON 解析失败:', error);showToast('数据格式不对,请检查输入');}// 但是 async/await 里的 try-catch 有点讲究asyncfunctionfetchUserData(userId){try{// 这里如果 fetchUser 抛错,会被 catch 住const user =awaitfetchUser(userId);// 如果 fetchOrders 抛错,也会被 catch 住const orders =awaitfetchOrders(user.id);return{ user, orders };}catch(error){// 问题来了:这里怎么知道是哪个接口挂了?// 简单业务还好,复杂业务得做错误分类if(error.name ==='NetworkError'){showToast('网络开小差了,戳这里重试');// 记录网络错误,可能是用户信号不好logError({type:'network', error });}elseif(error.response?.status ===404){showToast('用户不存在');// 业务错误,可能需要跳转 404 页面
      router.push('/404');}else{// 未知错误,抛给上层或者全局处理throw error;}}}// 更优雅的错误处理封装// 定义一个 Result 类型,类似 Rust 或者 Go 的错误处理风格classResult{constructor(ok, data, error){this.ok = ok;this.data = data;this.error = error;}// 成功时返回数据,失败时返回默认值unwrapOr(defaultValue){returnthis.ok ?this.data : defaultValue;}// 链式处理map(fn){returnthis.ok ?newResult(true,fn(this.data),null):this;}}// 包装异步操作,让它永不抛错,总是返回 ResultasyncfunctionsafeAsync(promise){try{const data =await promise;returnnewResult(true, data,null);}catch(error){returnnewResult(false,null, error);}}// 使用示例,代码清爽多了asyncfunctionloadPageData(){const userResult =awaitsafeAsync(fetchUser());if(!userResult.ok){
    console.error('获取用户失败:', userResult.error);return{error:'user_fetch_failed'};}const ordersResult =awaitsafeAsync(fetchOrders(userResult.data.id));// 即使 orders 挂了,user 数据还在,可以部分渲染return{user: userResult.data,orders: ordersResult.unwrapOr([])// 订单挂了显示空数组,别白屏};}

Vue 和 React 的专属结界

现代框架都给了组件级的错误边界,这简直是救命稻草。组件挂了不至于全站崩盘,用户至少能看到个"出错了"的友好提示,而不是整页白屏。

Vue 的 errorHandler 配置:

// Vue 2 的全局配置
Vue.config.errorHandler=function(err, vm, info){// err: 错误对象// vm: 出错的组件实例// info: 错误信息,比如 "render function" 或者 "v-on handler"
  
  console.error('Vue 捕获到错误:',{error: err.message,component: vm?.$options?.name ||'anonymous',info: info,// 可以拿到组件的 props 和 data,排查问题很有用props: vm?.$options?.propsData,data: vm?._data,// 组件调用栈trace: vm?.$options?.__file ||'unknown file'});// 上报错误reportError({type:'vue',message: err.message,stack: err.stack,component: vm?.$options?.name,info: info,timestamp: Date.now()});};// Vue 3 稍微变了一下,但思路一样import{ createApp }from'vue';const app =createApp(App);
app.config.errorHandler=(err, instance, info)=>{// 逻辑和上面差不多,instance 是组件实例
  console.error('[Vue3 Error]', err, info);// 这里可以配合全局状态管理,显示个错误提示组件// 比如把错误信息塞到 Pinia 或者 Vuex 里useErrorStore().setError({message:'页面渲染出错了,刷新试试?',detail: err.message
  });};// Vue 还提供了 warnHandler 专门抓警告,开发环境很有用
app.config.warnHandler=(msg, instance, trace)=>{// 警告一般不上报,但开发时可以打印详细点
  console.warn('[Vue Warning]', msg, trace);};

React 的 Error Boundary 稍微麻烦点,得用类组件:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
// 错误边界必须是类组件,React 官方说的,函数组件暂时不行
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false,
      error: null,
      errorInfo: null
    };
  }
  // 静态方法,返回新的 state,用于渲染降级 UI
  static getDerivedStateFromError(error) {
    // 下次渲染时显示备用 UI
    return { hasError: true, error };
  }
  // 组件DidCatch,用于记录错误信息
  componentDidCatch(error, errorInfo) {
    // errorInfo 包含 componentStack,能看到是哪个组件树出的问题
    console.error('ErrorBoundary 捕获到错误:', {
      error: error.toString(),
      componentStack: errorInfo.componentStack,
      // 可以记录当前的路由信息
      pathname: window.location.pathname,
      // 用户信息,如果有的话
      userId: localStorage.getItem('userId')
    });
    
    this.setState({
      errorInfo: errorInfo
    });
    
    // 上报错误
    reportError({
      type: 'react',
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo.componentStack,
      timestamp: new Date().toISOString()
    });
  }
  // 提供重置功能,让用户能尝试恢复
  handleReset = () => {
    this.setState({ 
      hasError: false, 
      error: null, 
      errorInfo: null 
    });
    // 可以调用父组件传入的重置回调
    this.props.onReset?.();
  };
  render() {
    if (this.state.hasError) {
      // 自定义降级 UI
      return (
        <div className="error-fallback" style={{
          padding: '40px',
          textAlign: 'center',
          background: '#fff2f0',
          border: '1px solid #ffccc7',
          borderRadius: '8px'
        }}>
          <h2 style={{ color: '#cf1322' }}>            
            
            

本文标签: 失败 编程 系统

更多相关文章

从192.168.1.1开始:Adobe Flash Player官方入口的全面解读

7天前

【登陆官网】网友提问:怎么用的呢?的时候,官网登陆不了怎么办?热心网友答:要进入192.1.1.1,需要手机连接路由器发射出来的Wi

告别重装系统,用DISM轻松解决电脑问题

7天前

介绍了解: DISM(部署映像服务和管理)是三种 Windows 诊断工具中最强大的。当遇到频繁的崩溃、冻结和错误,或者 SFC 要么无法修复您的系统文件,或者根本无法运行时,可以使用该工具。 相连文章: 修复

玩转Dism++,打造流畅的电脑体验

7天前

简介:Dism++是一款集成多种功能的Windows系统优化管理工具,提供从更新补丁管理到系统封装的一站式服务。它以高效、稳定和易用性获得了IT爱好者的广泛好评。本文将详细介绍Dism++的核心功能,包括系统更新补丁管理、垃圾清理、系

Dism助力:快速上手实现Flash Player无缝安装与更新

7天前

相关文章推荐:Windows ADK 下载地址: 命令示例:Gimagex图形化演示:以下命令由DISMGUI生成,原汁原味1.首次备份镜像【Captu

Dism++:让你的电脑焕然一新,快速提升性能,告别延迟!

7天前

无需全家桶,不占内存,5MB的绿色工具让你的Windows流畅如新!在Windows系统长期使用过程中,系统臃肿、运行卡顿、C盘爆满等问题困扰着绝大多数用户。面对这些痛点,很多人的第一反应是重装系统,但今天我将介绍一款更

0x800736cc让你头疼?用DISM让你的Windows更新畅通无阻

7天前

在server 2012系统上安装IIS时报了一个错误,错误代码为0x800736cc,查了一下官方社区发现这个问题是系统被一些优化工具优化时或者一些其他操作造成了系统文件损坏,造成系统不能安装更新(安装IIS也是一个系统安装更新的过

告别系统崩溃,通过DISM工具让电脑重获新生

7天前

介绍了解: DISM(部署映像服务和管理)是三种 Windows 诊断工具中最强大的。当遇到频繁的崩溃、冻结和错误,或者 SFC 要么无法修复您的系统文件,或者根本无法运行时,可以使用该工具。 相连文章: 修复

一文读懂Dism命令行,Adobe Flash Player安装不再难!

7天前

相关文章推荐:Windows ADK 下载地址: 命令示例:Gimagex图形化演示:以下命令由DISMGUI生成,原汁原味1.首次备份镜像【Captu

告别Flash播放器错误,用DISM轻松搞定

7天前

在win10系统中,当系统出现文件受损或丢失后,可以使用DISM工具进行联机修复:1、使用管理员运行CMD: DISM Online Cleanup-image RestoreHealth命令会联机下载并修

告别繁琐,Dism++一键卸载驱动,让电脑运行更流畅

7天前

资源说明 Dism++(系统精简利器)是一款功能全面的Windows系统精简工具,在某种程度上可以说是以前的Dism管理器的升级版(最开始的名字叫Windows更新清理工具),Dism++(系统精简利器)全新的构建,更小的体积

Dism++:你的日常维护与系统优化好帮手

7天前

简介:Dism++是一款先进的系统维护工具,专注于清理电脑垃圾、释放内存,提供全面的系统优化解决方案。最新版本Dism++10.1.1000.100_2d2bf466baca088c4b35248f5a7316f4e00cac0b特别

Dism++优化秘籍:一步到位提升电脑运行速度

7天前

1.系统文件清理 虽然dism的文件清理比较弱,但相对于其他清理工具来说,清理系统垃圾文件功能比较丰富,选择软件的空间回收栏目,勾选所有的清理功能,点击扫描,稍等片刻,即可扫描出不需要的文件,点击清理即可。 其中需要注

Windows备份不求人:自助指南助你一臂之力

7天前

win系统环境搭建(十五)——如何将Windows系统备份 1.为什么要做备份?windows蓝屏警告!!!

Windows Server系统备份与恢复:实战教程

7天前

1、添加windows server backup功能 a)选择添加角色和功能 b)选择功能中勾选“windows server backup”,然后“下一步” c)安装功能 2、使用windows s

Linux备份与恢复必修课:SWF文件安全策略从入门到精通

7天前

在linux工作,系统备份是很有必要的,养成系统备份的好习惯会提高你的工作效率。下面我就简单的说一下:1.备份系统首先成为root用户:sudo su然后进入文件系统的根目录

省时省心!三步完成电脑系统高效备份!

7天前

电脑系统备份方法 当今时下系统备份已经越来越被广大网友们所使用,做好了系统备份,就相当于给你的电脑系统加了一个保护伞或者买了份保险。 电脑系统备份的重要性已经尤为明显,提前做好了 的朋友可以不用担心电脑

Ubuntu系统安全大计,备份技巧大公开

7天前

本文主要参考这个博客。全文一半内容是复制粘贴的这个博客内容,提前声明一下,以防侵权。还参考了下这个ubuntu有时候用着用着崩了,或者想回退到历史某个版本。这就需要系统备份了:把当前某个能用的状态备

Linux系统不哭:高效备份与快速恢复方案

7天前

备份系统1.先切换到root用户sudosu 2.进入根目录tar cvpzf backup.tgz --exclude=proc --exclude=lost+found --exclude=ba

Ubuntu系统维护秘籍:备份步骤详解,保护你的劳动成果!

7天前

记录ubuntu的系统备份方法: 测试平台:ubuntu16.04,已安装nvidia384 cuda opencv protobuf等等运算库。使用ubuntu时经常需要重新安装电脑,和windows不一样的

SWF文件备份失败?这些步骤让你轻松搞定

7天前

数据备份与恢复、系统备份与恢复 一、数据备份与恢复 1、什么是备份 备份,即另外准备一 – 为应付文件、数据丢失或损坏等可能出现的意外情况,将电子计算机存储设备中的数据复制到大容量存储设备中 2

发表评论

全部评论 0
暂无评论