Web前端最全剖析前端异常及降级处理,Web前端面试数据结构算法

最后

编程基础的初级开发者,计算机科学专业的学生,以及平时没怎么利用过数据结构与算法的开发人员希望复习这些概念为下次技术面试做准备。或者想学习一些计算机科学的基本概念,以优化代码,提高编程技能。这份笔记都是可以作为参考的。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

名不虚传!字节技术官甩出的"保姆级"数据结构与算法笔记太香了

console.log(‘I want running’)

复制代码

代码一执行,你猜怎么着?第一行语句报错了,第二行语句的log也就没打印出来。如果我们把代码改成这样:

try{

console.log(foo)

}catch(e){

console.log(e)

}

console.log(‘I want running’);

复制代码

以上代码执行之后,虽然还是报了个ReferenceError错误,但是后面的log却能够被执行。

从这个示例,我们可以看出,一旦前面的(同步)代码出现了没有被开发者捕获的异常的话,那么后面的代码就不会执行了。所以,如果你希望当前可能出错的代码块后续的代码能够正常运行的话,那么你就得使用try…catch来主动捕获异常。

扩展:

实际上,出错代码是如何干扰后续代码的执行,是一个值得探讨的主题。下面进行具体的探讨。因为市面上浏览器众多,对标准的实现也不太一致。所以,这里的结论仅仅是基于Chromev91.0.4472.114。探讨过程中,我们涉及到两组概念:同步代码与异步代码,代码书写期和代码运行期。

场景一:同步代码(出错) + 同步代码

1625024247(1).png

可以看到,出错的同步代码后面的同步代码不执行了。

场景二:同步代码(出错) + 异步代码

1625024396(1).png

跟上面的情况一下,异步代码也受到影响,也不执行了。

场景三:异步代码(出错) + 同步代码

image.png

可以看到,异步代码出错,并不会影响后面同步代码的执行。

场景四:异步代码(出错) + 异步代码

image.png

出错的异步代码也不会影响后面异步代码的执行。

如果只看场景一二三,很容易得出如下结论:在代码运行期,同步代码始终是先于异步代码执行的。如果先执行的同步代码没有出错的话,那么后面的代码就会正常执行,否则后面的代码就不会执行。但场景四却打破了这个结论。我们不妨继续看看场景五。

场景五:异步代码 + 同步代码(出错) + 异步代码

image.png

看到了没?同样是异步代码,按理说,代码运行期,如果你是受出错的同步代码的影响的话,那你要么是两个都不执行,或者两个都执行啊?凭什么写在出错代码代码书写期前面的异步代码就能正常执行,而写在后面的就不执行呢?经过验证,在firefoxv75.0版本中也是同样的表现。

所以,到了这里,我们基本上可以得出这样的结论:运行期,一先一后的两个代码中,出错的一方代码是如何影响另外一方代码继续执行的问题中,跟异步代码没关系,只跟同步代码有关系;跟代码执行期没关系,只跟代码书写期有关系。

说人话就是,异步代码出错与否都不会影响其他代码继续执行。而出错的同步代码,如果它在代码书写期是写在其他代码之前,并且我们并没有对它进行手动地去异常捕获的话,那么它就会影响其他代码(不论它是同步还是异步代码)的继续执行。

综上所述,如果我们想要保证某块可能出错的同步代码后面的代码继续执行的话,那么我们必须对这块同步代码进行异常捕获。

3.范围

只能捕获同步代码所产生的运行时错误,对于语法错误和异步代码所产生的错误是无能为力的。

当遇到语法错误时:

当遇到异步运行时错误时:

(2) Promise.catch()

1.语法

const promise1 = new Promise((resolve, reject) => {

throw ‘Uh-oh!’;

});

promise1.catch((error) => {

console.error(error);

});

// expected output: Uh-oh!

复制代码

2.动机

用来捕获promise代码中的错误

3.范围

使用Promise.prototype.catch()我们可以方便的捕获到异常,现在我们来测试一下常见的语法错误、代码错误以及异步错误。

当遇到代码错误时,可以捕获:

当遇到语法错误时,不能捕获:

当遇到异步运行时错误时,不能捕获:

1625033576(1).png

(3) unhandledrejection

1.用法

unhandledrejection:当Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件

window.addEventListener(“unhandledrejection”, function(e){

console.log(e);

});

复制代码

2.动机

为了防止有漏掉的 Promise 异常,可以在全局增加一个对 unhandledrejection 的监听进行兜底,用来全局监听Uncaught Promise Error。

3.范围

window.addEventListener(“unhandledrejection”, function (e) {

console.log(“捕获到的promise异常:”, e);

e.preventDefault();

});

new Promise((res) => {

console.log(a);

});

// 捕获到的promise异常的: PromiseRejectionEvent

复制代码

注意:此段代码直接写在控制台是捕获不到promise异常的,写在html文件中可正常捕获。

(4) window.onerror

1.用法

当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件,并执行 window.onerror()。

window.onerror = function(message, source, lineno, colno, error) {

console.log(‘捕获到异常:’,{message, source, lineno, colno, error});

}

复制代码

2.动机

众所周知,很多做错误监控和上报的类库就是基于这个特性来实现的,我们期待它能处理那些try…catch不能处理的错误。

3.范围

根据MDN的说法,wondow.onerror能捕获JavaScript运行时错误(包括语法错误)或一些资源错误。而在真正的测试过程中,wondow.onerror并不能捕获语法错误。

image.png

经测试,window.onerror并不能捕获语法错误和静态资源的加载错误。同样也不能捕获异步代码的错误,但是有一点值得注意的是,window.onerror能捕获同样是异步代码的setTimeout和setInterval里面的错误。

看来,寄予厚望的window.onerror并不是万能的。

(5) window.addEventListener

1.用法

window.addEventListener(‘error’,(error)=>{console.log(error)})

复制代码

2.动机

当然是希望用他来兜住window.onerror和try catch的底,希望他能捕获到异步错误和资源的加载错误。

3.范围

复制代码

在此过程中,资源文件都是不存在的,我们发现window.addEventListener(‘error’)依旧不能捕获语法错误,Promise异常和iframe异常。

对于语法错误我们可以在编译过程中捕获,,Promise异常已在上文中给出解决方案,现在还剩下iframe异常需要单独处理了。

(5) iframe异常

1.用法

window.frames[0].onerror = function (message, source, lineno, colno, error) {

console.log(‘捕获到 iframe 异常:’,{message, source, lineno, colno, error});

return true;

};

复制代码

2.动机

用来专门捕获iframe加载过程中的异常。

3.范围

很遗憾,结果并不令人满意,在实际的测试过程中,该方法未能捕获到异常。

(6) React中捕获异常

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。

错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

注意:错误边界无法捕获以下场景中产生的错误

  • 事件处理

  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)

  • 服务端渲染

  • 它自身抛出来的错误(并非它的子组件)

如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。

class ErrorBoundary extends React.Component {

constructor(props) {

super(props);

this.state = { hasError: false };

}

static getDerivedStateFromError(error) {

// 更新 state 使下一次渲染能够显示降级后的 UI

return { hasError: true };

}

componentDidCatch(error, errorInfo) {

// 你同样可以将错误日志上报给服务器

logErrorToMyService(error, errorInfo);

}

render() {

if (this.state.hasError) {

// 你可以自定义降级后的 UI 并渲染

return 

Something went wrong.

;

}

return this.props.children;

}

}

复制代码

错误边界的工作方式类似于 JavaScript 的 catch {},不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。

以上引用自React 官网。

(7) Vue中捕获异常

Vue.config.errorHandler = function (err, vm, info) {

// handle error

// info 是 Vue 特定的错误信息,比如错误所在的生命周期钩子

// 只在 2.2.0+ 可用

}

复制代码

指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。

  • 从 2.2.0 起,这个钩子也会捕获组件生命周期钩子里的错误。同样的,当这个钩子是 undefined 时,被捕获的错误会通过 console.error 输出而避免应用崩溃。

  • 从 2.4.0 起,这个钩子也会捕获 Vue 自定义事件处理函数内部的错误了。

  • 从 2.6.0 起,这个钩子也会捕获 v-on DOM 监听器内部抛出的错误。另外,如果任何被覆盖的钩子或处理函数返回一个 Promise 链 (例如 async 函数),则来自其 Promise 链的错误也会被处理。

以上引用自Vue 官网。

(8) http请求异常

1.用法

以axios为例,添加响应拦截器

axios.interceptors.response.use(function (response) {

// 对响应数据做点什么

// response 是请求回来的数据

return response;

}, function (error) {

// 对响应错误做点什么

return Promise.reject(error)

}

)

复制代码

2.动机

用来专门捕获HTTP请求异常

五、项目实践


在提出了这么多的解决方案之后,相信大家对具体怎么用还是存在一些疑惑。那么接下来,我们真正的进入实践阶段吧!

我们再次回顾一下我们需要解决的问题是什么?

  • 语法错误

  • 事件异常

  • HTTP请求异常

  • 静态资源加载异常

  • Promise 异常

  • Iframe 异常

  • 页面崩溃

捕获异常是我们的最终目标吗?并不是,回到解决问题的背景下,相比于页面崩溃或点不动,在适当的时机,以一种适当的方式去提醒用户当前发生了什么,无疑是一种更友好的处理方式。

结合到项目中,具体实践起来有如下两种方案:

  • 1.代码中通过大量的try catch/Promise.catch来捕获,捕获不到的使用其他方式进行兜底

  • 2.通过框架提供的机制来做,再对不能捕获的进行兜底

方案一无疑不是很聪明的样子…这意味着要去改大量的原有代码,心智负担成倍数增加。方案二则更加明智,通过在底层对错误进行统一处理,无需变更原有逻辑。

到项目中,使用的是React框架,React正好提供了一种捕获异常的机制(上文已提及)并做降级处理,但是细心的小伙伴发现了,react并不能捕获如下四种错误:

  • 事件处理

  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)

  • 服务端渲染

  • 它自身抛出来的错误(并非它的子组件)

对于第三点服务端渲染错误,项目中并没有适用的场景,此次不做重点分析。我们重点分析第一点和第二点。

我在这里先抛出几个问题,大家先做短暂的思考:

  • 1.若事件处理和异步代码的错误导致页面crash,我们该如何预防?

  • 2.如何对ErrorBounary进行兜底?相比一个按钮点击无效,如何更加友好的提示用户?

先来看第一个问题,若事件处理和异步代码的错误导致页面崩溃:

const Test = () => {

const [data, setData] = useState([]);

return (

onClick={() => {

setData(‘’);

}}

{data.map((s) => s.i)}

);

};

复制代码

此段代码在正常渲染期间是没问题的,但在触发了点击事件之后会导致页面异常白屏,如果在外面套上我们的ErrorBounday组件,情况会是怎么样呢?

答案是依然能够捕获到错误,并能够对该组件进行降级处理!

此时有些小伙伴已经察觉到了,错误边界只要是在渲染期间都是可以捕获错误的,无论首次渲染还是二次渲染。流程图如下:

image.png

第一个问题原来根本就不是问题,这本身就是一个闭环,不用我们解决!

再来看看第二个问题:

对于事件处理和异步代码中不会导致页面崩溃的代码:

const Test = () => {

return (

<button

onClick={() => {

[].map((s) => s.a.b);

}}

点击

);

};

复制代码

button按钮可正常点击,但是该点击事件的内部逻辑是有问题的,导致用户点击该按钮本质是无效的。此时若不及时给与友好提示,用户只会陷入抓狂中…

那么有没有办法对ErrorBoundary进行兜底呢?即可以捕获异步代码或事件处理中的错误。

上文提到的window.addEventListener(‘error’)正好可以解决这个问题。理想状态下:

而真正的执行顺序确实这样的:

最后

小编综合了阿里的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

虽只说了一个公司的面试,但我们可以知道大厂关注的东西并举一反三,通过一个知识点延伸到另一个知识点,这是我们要掌握的学习方法,小伙伴们在这篇有学到的请评论点赞转发告诉小编哦,谢谢大家的支持!

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值