最后
面试题千万不要死记,一定要自己理解,用自己的方式表达出来,在这里预祝各位成功拿下自己心仪的offer。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
(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.范围
![](./fake.png)
复制代码
在此过程中,资源文件都是不存在的,我们发现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’)正好可以解决这个问题。理想状态下:
而真正的执行顺序确实这样的:
1625105438(1).png
在真正执行的过程中,window.addEventListener(‘error’)是先于ErrorBoundary捕获到错误的,这就导致当error事件捕获到错误时,他并不知道该错误是否会导致页面崩溃,不知道该给予怎样的提示,到底是对页面进行降级处理还是只做简单的报错提示?
问题似乎就卡在这了…
那能否通过一种有效的途径告诉error事件:ErrorBoundary已经捕获到了错误,你不需要处理!亦或者是ErrorBoundary未能捕获到错误,这是一个异步错误/事件错误,但不会引起页面崩溃,你只需要提示用户!
答案肯定是有的,比如建立一个nodeJs服务器,通过webSocket去通知,但是这样做不仅麻烦,还会有一定的延迟。
在笔者苦思冥想之际,在某个静悄悄的夜晚,突然灵感一现。为什么我们非要按照他规定的顺序执行呢?我们能不能尝试改变他的执行顺序,让错误捕获回到我们理想中的流程来呢?
改变思路之后,我们再思考有什么能改变代码执行顺序吗?没错,异步事件!
window.addEventListener(‘error’, function (error) {
setTimeout(()=>{
console.log(error, ‘error错误’);
})
});
复制代码
当给error事件的回调函数加入setTimeout后,捕获异常的流程为:
image.png
现在就可以通知error事件到底页面崩溃了没有,到底需不需要它的处理!上代码:
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);
//告诉error事件 ErrorBoundary已处理异常
localStorage.setItem(“ErrorBoundary”,true)
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return
Something went wrong.
;}
return this.props.children;
}
}
复制代码
window.addEventListener(‘error’, function (error) {
setTimeout(() => {
//进来代表一定有错误 判断ErrorBoundary中是否已处理异常
const flag = localStorage.getItem(‘ErrorBounary’);
if (flag) {
//进入了ErrorBounary 错误已被处理 error事件不用处理该异常
localStorage.setItem(‘ErrorBounary’, false); //重置状态
} else {
//未进入ErrorBounary 代表此错误为异步错误/事件错误
logErrorToMyService(error, errorInfo); // 你可以将错误日志上报给服务
//判断具体错误类型
if (error.message.indexOf(‘TypeError’)) {
alert(‘这是一个TypeError错误,请通知开发人员’);
} else if (error.message.indexOf(‘SyntaxError’)) {
alert(‘这是一个SyntaxError错误,请通知开发人员’);
} else {
算法刷题
大厂面试还是很注重算法题的,尤其是字节跳动,算法是问的比较多的,关于算法,推荐《LeetCode》和《算法的乐趣》,这两本我也有电子版,字节跳动、阿里、美团等大厂面试题(含答案+解析)、学习笔记、Xmind思维导图均可以分享给大家学习。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
写在最后
最后,对所以做Java的朋友提几点建议,也是我的个人心得:
-
疯狂编程
-
学习效果可视化
-
写博客
-
阅读优秀代码
-
心态调整
ndary中是否已处理异常
const flag = localStorage.getItem(‘ErrorBounary’);
if (flag) {
//进入了ErrorBounary 错误已被处理 error事件不用处理该异常
localStorage.setItem(‘ErrorBounary’, false); //重置状态
} else {
//未进入ErrorBounary 代表此错误为异步错误/事件错误
logErrorToMyService(error, errorInfo); // 你可以将错误日志上报给服务
//判断具体错误类型
if (error.message.indexOf(‘TypeError’)) {
alert(‘这是一个TypeError错误,请通知开发人员’);
} else if (error.message.indexOf(‘SyntaxError’)) {
alert(‘这是一个SyntaxError错误,请通知开发人员’);
} else {
算法刷题
大厂面试还是很注重算法题的,尤其是字节跳动,算法是问的比较多的,关于算法,推荐《LeetCode》和《算法的乐趣》,这两本我也有电子版,字节跳动、阿里、美团等大厂面试题(含答案+解析)、学习笔记、Xmind思维导图均可以分享给大家学习。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
写在最后
最后,对所以做Java的朋友提几点建议,也是我的个人心得:
-
疯狂编程
-
学习效果可视化
-
写博客
-
阅读优秀代码
-
心态调整