# JavaScript try-catch 处理错误和异常指南 在任何软件开发项目中,处理错误和异常对应用程序的成功至关重要。无论你是新手还是专家,你的代码都可能因为各种原因而失败,比如简单的拼写错误或来自外部服务的意外行为。

因为你的代码总是有失败的可能,所以你需要通过使你的代码更加健壮来准备处理这种情况。你可以通过多种方式做到这一点,但一种常见的解决方案是利用 try-catch 语句。这个语句允许你包装一个代码块来尝试和执行。如果在执行过程中发生了任何错误,该语句将“捕获”它们,你可以快速修复它们,避免应用程序崩溃。

本指南将作为 try-catch 语句的实用介绍,并向你展示如何使用它们处理 JavaScript 中的错误。

1. 为什么需要 try-catch

在学习 try-catch 语句之前,你需要了解 JavaScript 中的整体错误处理。在处理 JavaScript 中的错误时,可能会出现几种类型的错误。在本文中,你将重点关注两种类型:语法错误运行时错误。在 深入了解 JavaScript 语法错误以及如何防止它们 中你可以了解更多关于语法错误的信息。

当你不遵循特定编程语言的规则时,就会发生语法错误。可以通过配置 linter 来检测这些错误,这是一种用来分析代码并标记风格错误或编程错误的工具。例如,你可以使用 ESLint 来查找和修复代码中的问题。

下面是一个语法错误的例子:

console.log(;)

在这种情况下,发生错误是因为代码的语法不正确。它应该如下:

console.log('Some Message');

当应用程序在运行时出现问题时,就会发生运行时错误。例如,你的代码可能试图调用一个不存在的方法。要捕获这些错误并应用一些处理,可以使用 try-catch 语句。

2. 什么是异常

异常是表示在程序执行时发生了问题的对象。当访问无效数组索引时,当试图访问空引用的某些成员时,当试图调用不存在的函数时,都可能发生这些问题。

例如,考虑一个应用程序依赖第三方 API 的场景。你的代码可能会处理来自这个 API 的响应,期望它们包含某些属性。如果这个 API 由于任何原因返回一个意外的响应,它可能会触发一个运行时错误。在这种情况下,你可以将受影响的逻辑包装在 try-catch 语句中,并向用户提供错误消息,甚至调用一些回退逻辑,而不是允许错误导致应用程序崩溃。

3. try-catch 是如何运行的

简单地说,一个 try-catch 语句包含两个代码块——一个以 try 关键字为前缀,另一个以 catch 关键字为前缀——以及一个用于在其中存储错误对象的变量名。如果 try 块内部的代码抛出错误,则该错误将被传递到 catch 块进行处理。如果它不抛出错误,则永远不会调用 catch 块。考虑下面的例子:

try {nonExistentFunction()
} catch (error) {console.log(error); // [ReferenceError: nonExistentFunction is not defined]
}

在本例中,当函数被调用时,运行时将发现没有这样的函数并抛出错误。多亏了围绕它的 try-catch 语句,这个错误不是致命的,可以按照你喜欢的方式进行处理。在本例中,它被传递到 console.log,它告诉你错误是什么。

还有另一个可选语句称为 finally 语句,当它出现时,总是在 trycatch 之后执行,而不管是否执行了catch。它通常用于包含释放在 try 句期间分配的资源的命令,这些资源在发生错误时可能没有机会优雅地清理。考虑下面的例子:

openFile();
try { writeData();
} catch (error) { console.log(error);
} finally { closeFile();
}

在这个人为的例子中,假设在 try-catch-finally 语句之前打开了一个文件。如果在 try 块期间发生了错误,那么一定要关闭文件,以避免内存泄漏或死锁。在本例中,finally 语句确保无论 try-catch 如何执行,文件都将在继续之前关闭。

当然,用 try-catch 语句包装可能容易出错的代码只是容错难题的一部分。另一部分是知道在抛出错误时该如何处理。

有时将它们显示给用户可能是有意义的(通常以更容易读懂的格式),有时你可能想简单地记录它们以供将来参考。无论哪种方式,熟悉 Error 对象本身都有帮助,这样你就知道必须处理哪些数据。

4. Error 对象

每当 try 语句中抛出异常时,JavaScript 会创建一个 Error 对象并将其作为参数发送给 catch 语句。通常,这个对象有两个主要的实例属性:

  • name:描述错误类型的字符串
  • message:更详细的错误描述
try { nonExistentFunction();
} catch (error) {const {name, message} = error;console.log({ name, message }) // { name: 'ReferenceError', message: 'nonExistentFunction is not defined' }
}

如前所述,name 属性的值引用发生的 错误类型。以下是一些比较常见的错误类型的非详尽列表:

  • ReferenceError:当检测到对不存在的或无效的变量或函数的引用时抛出ReferenceError
  • TypeError:当一个值以与其类型不兼容的方式使用时,例如试图调用一个数字的字符串函数((1).split(',');), TypeError 将被抛出。
  • SyntaxError:当在解释代码时出现语法错误时抛出 SyntaxError;例如,当解析 JSON 时使用后面的逗号(JSON.parse('[1,2,3,4,]');)。
  • SyntaxError:当 URI 处理中发生错误时抛出 URIError;例如,在decodeURI()encodeURI() 中传入无效的参数。
  • RangeError:当一个值不在允许值的集合或范围内时抛出 RangeError;例如,数字数组中的字符串值。

所有原生 JavaScript 错误都是通用 Error 对象的扩展。根据这个原则,你还可以创建自己的错误类型。

5. 自定义 Errors

JavaScript 中另一个与错误和错误处理密切相关的关键字是 throw。使用此关键字时,可以“抛出”用户定义的异常。当你这样做时,当前函数将停止执行,并且与 throw 关键字一起使用的任何值将被传递给调用堆栈中的第一个 catch 语句。如果没有 catch 语句来处理它,行为将类似于典型的未处理错误,程序将终止。

抛出自定义错误在更复杂的应用程序中很有用,因为它为你提供了对代码进行流控制的另一种途径。考虑这样一个场景,你需要验证一些用户输入。如果根据业务规则认为输入无效,则不希望继续处理请求。这是 throw 语句的完美用例。考虑下面的例子:

// 你可以通过扩展泛型类来定义自己的错误类型
class ValidationError extends Error {constructor(message) {     super(message);this.name = 'ValidationError';}
}const userInputIsValid = false;
try {if (!userInputIsValid) {// 手动触发自定义错误throw new ValidationError('User input is not valid'); }
} catch (error) {const { name, message } = error;console.log({ name, message }); // { name: 'ValidationError', message: 'User input is not valid' }
}

这里定义了一个自定义错误类,可以扩展泛型错误类。这种技术可用于抛出与业务逻辑专门相关的错误,而不仅仅是 JavaScript 引擎使用的默认错误。

6. 什么时候用 try-catch

因为错误会沿着调用堆栈向上攀升,直到它们找到一个 try-catch 语句或应用程序终止,所以很容易将整个应用程序简单地封装在一个大型的 try-catch 语句或许多较小的 try-catch 语句中,这样你就可以享受应用程序在技术上不会再次崩溃的事实。然而这不是一个好主意。

当涉及到编程时,错误是一个事实,它们在应用程序的生命周期中扮演着不可忽视的重要角色。他们会告诉你哪里出了问题。因此,如果你想为用户提供良好的体验,就必须尊重并谨慎地使用 try-catch 语句。

一般应该在合理预期可能发生错误的地方使用 try-catch 语句。但是,一旦捕捉到错误,通常不会想要直接删除它。如前所述,当抛出错误时,就意味着发生了错误。你应该利用这个机会适当地处理错误,无论是在 UI 中向用户显示更好的错误消息,还是将错误发送到应用程序监视工具(如果有的话),以便进行聚合和稍后的分析。

通常,在 try-catch 语句中包装代码时,应该只包装在概念上与预期错误相关的代码。如果函数的大小相当小,这可能意味着要包装整个函数体。或者,你可能有一个更大的函数,其中主体中的所有代码都被包装了,但分散在多个 try-catch 语句中。这实际上可以作为一种指示,表明有问题的功能过于复杂和或处理太多的责任。这样的代码可以被分解为更小、更集中的函数。

对于代码中不太可能出现错误的区域,通常最好是放弃过多的 try-catch 语句,直接允许错误发生。这听起来可能违反直觉,但允许代码快速失败,实际上是让自己处于一个更好的位置。压缩错误可能会带来稳定的外观,但仍然会有潜在的问题。

允许错误在没有显式处理的情况下发生意味着当与应用程序监视工具如 BugSnag 或 Sentry 配合使用,你可以全局地拦截和记录错误,以供以后分析。这些工具可以让你看到应用程序中错误的实际位置,以便你能够修复它们,而不是盲目地忽略它们。

7. 异步函数

要理解 JavaScript 中的异步函数是什么,就必须理解 Promise 是什么。

promise 本质上是表示异步操作最终完成的对象。它们定义了将来要执行的操作,最终将 resolve(成功)或 reject(错误)。

例如,下面的代码显示了快速解析值的简单 Promise。然后将该值传递给 then 回调函数:

// 这个 Promise 将 resolve
new Promise((resolve, reject) => { const a = 10; const b = 9; resolve(a + b);
})
.then(result => {console.log(result); // 19
});

下一个例子展示了在 Promise 中抛出的错误将如何由 Promise catch 回调处理:

new Promise((resolve, reject) => {const a = 10; const b = 9; throw new Error('manually thrown error')resolve(a + b); // 这里没有执行到
})
.then(result => { console.log(result); // 这个永远不会被执行
})
.catch(error => {console.log('something went wrong', error) // Something went wrong [Error: manually thrown error]
})

你可以使用 reject 函数,而不是在 promise 中手动抛出错误。这个函数作为 Promise 的回调函数的第二个参数提供:

new Promise((resolve, reject) => {const a = 10; const b = 9; reject('- manual rejection'); console.log(a + b); // 19resolve(a + b);
})
.then(result => { console.log(result); // 这个永远不会被执行
})
.catch(error => {console.log('something went wrong', error) // something went wrong - manual rejection
})

你可能会在最后一个例子中注意到一些奇怪的东西。抛出错误reject 是有区别的。抛出错误将停止代码的执行,并将错误传递给最近的 catch 语句或终止程序。reject 一个 Promise 将调用 catch 回调函数,但如果有更多的代码要运行,它不会停止 Promise 的执行,除非对 reject 函数的调用带有返回关键字的前缀。这就是为什么 console.log(a + b); 即使 reject,声明仍然被触发。要避免这种行为,只需使用 return reject(…) 提前结束执行即可。

8. 总结

try-catch 语句是一个很有用的工具,在程序员的职业生涯中肯定会用到。然而,任何不加选择地使用的方法都可能不是最好的解决方案。记住,你需要使用正确的工具和概念来解决特定的问题。例如,当你不希望发生任何异常时,通常不会使用 try-catch 语句。如果在这些情况下确实发生了错误,你可以在出现错误时进行识别和修复。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

75道关于CSS的高频面试题总结,请注意查收相关推荐

  1. 《金三银四》奉上35道胖乎乎的Kubernetes高频面试题

    <金三银四>奉上35道胖乎乎的Kubernetes高频面试题 微服务应用容器化时代,时代之子Kubernetes-企业级容器应用托管

  2. why哥这里有一道Dubbo高频面试题,请查收。

    这是why的第 64 篇原创文章 荒腔走板 大家好,我是 why,欢迎来到我连续周更优质原创文章的第 64 篇.老规矩,先荒腔走板聊聊其他的. 上面这图是我之前拼的一个拼图. 我经常玩拼图,我大概拼了 ...

  3. JS高频面试题,请查阅,务必收藏持续更新

    this的指向? this:是函数体内的内置对象,作用域在函数体内 1.与事件体连用,代表触发该事件的元素 2.与普通方法连用,代表调用该方法的对象 3.与构造方法连用,代表new出来的实力化对象 4 ...

  4. 100道Java高频面试题(阿里面试官整理)

    我分享文章的时候,有个读者回复说他去年就关注了我的微信公众号,打算看完我的所有文章,然后去面试,结果我后来很长时间不更新了...所以为了弥补一直等我的娃儿们,给大家的金三银四准备了100道花时间准备的 ...

  5. 2022最新CSS高频面试题指南

    前言 大家好,我是CoderBin,本文将总结75道前端面试CSS中的高频考点,帮助同学们力闯秋招,赶快收藏起来学习啦

  6. Java 晋升必会的 70 道 「Spring 全家桶」高频面试题

    对于那些想面试高级 Java 岗位的同学来说,除了算法属于比较「天方夜谭」的题目外,剩下针对实际工作的题目就属于真正的本事了,热门技术的细节和难点成为了面试时主要考察的内容. 这里说「天方夜谭」并不是 ...

  7. Java并发编程75道面试题及答案

    1.在java中守护线程和本地线程区别?java中的线程分为两种:守护线程(Daemon)和用户线程(User).任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bo ...

  8. 面试突击 | 彻底搞定 JVM 这几道高频面试题

    前言 Java 相比 C/C++ 最显著的特点便是引入了自动垃圾回收 (下文统一用 GC 指代自动垃圾回收),它解决了 C/C++ 最令人头疼的内存管理问题,让程序员专注于程序本身,不用关心内存回收这 ...

  9. mybatis 带分号批量sql_请查收,32 道 MyBatis 的高频面试题已答完

    在网上找了 20 几道 MyBatis 的高频面试题,另补充了一些自己工作中遇到觉得比较重要的问题,共 32 道,如下: 介绍一下 MyBatis Mybaits 的优缺点 MyBatis 的适用场景 ...

最新文章

  1. 第三方支付接口的技术比较研究
  2. 比特币源码研读(4)数据结构-交易池TransactionPool
  3. socket编程--sockaddr_in结构体操作
  4. C语言实现TEA系列加解密算法
  5. 禅道11.0windows本机安装
  6. [cerc2012][Gym100624A]20181013
  7. 11个前端开发者必备的网站
  8. Mac OSX 下 mysql 影响关系的问题处理
  9. python声明编码_Python 2.x 编码声明:是coding:utf-8还是coding=urf-8呢
  10. ros中web端通过 ajax 访问 nginx 加载静态 pgm 地图显示在canvas画布中
  11. iOS自带的GPS 定位
  12. GIS应用知识解读!
  13. 怎样用软件测试主机电源,电脑电源功率怎么测试
  14. 黑眼圈大神程序员用5000字带你通透读懂Elasticsearch的注意事项
  15. 主流游戏引擎分析 【端游 、页游 、手游 解析】
  16. 小米硬盘路由器服务器,随手分享攻略 篇九:小米路由器更换10T硬盘 —— 小白教程...
  17. 微信公众号、小程序和企业微信申请流程
  18. 夏至 | 心怀热忱,认真生活 ;不负时光,不负自己
  19. crypto-music is frequency(INS‘hAck CTF 2018)
  20. phpinfo函数的使用

热门文章

  1. js简单实现剪刀石头布
  2. opengl实现百叶窗效果
  3. 用十六年时间,造一座声音“博物馆”:OPPO的影音进击之路
  4. 无线传感器原理及方法|重点理论知识|2021年19级|期末考试
  5. RHCSA--Vim 文件查看和查找文件
  6. 计算机主机箱,计算机主机箱的制作方法
  7. 北大、清华教授在线分享,机器人视觉、城市智能管理等主题报告,邀您“在线+免费”观看...
  8. java微信获取用户信息接口_java微信接口之二—获取用户组
  9. 奇偶位交换 牛客网 程序员面试金典 C++ Python
  10. 每日一练(2):二级题库