当前位置: 首页 > news >正文

JavaScript try-catch 处理错误和异常指南

JavaScript try-catch 处理错误和异常指南

  • JavaScript try-catch 处理错误和异常指南
    • 1. 为什么需要 try-catch
    • 2. 什么是异常
    • 3. try-catch 是如何运行的
    • 4. Error 对象
    • 5. 自定义 Errors
    • 6. 什么时候用 try-catch
    • 7. 异步函数
    • 8. 总结

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); // 19
    resolve(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 语句。如果在这些情况下确实发生了错误,你可以在出现错误时进行识别和修复。

相关文章:

  • Python文件的读写及常用文件的打开方式
  • MyBatis 中 #{} 和 ${} 的区别看完这篇文章一目了然
  • 实时即未来,车联网项目之原始终端数据实时ETL【二】
  • python 的re.findall的Bug以及解决方法
  • 在Windows系统上部署DHCP服务器
  • Java多线程~CAS的原理及其应用
  • [CSS]盒子模型
  • 【 C++ 】开散列哈希桶的模拟实现
  • 阿里云数据库(RDS)查看空间使用情况
  • 【C++编程语言】之 文件操作
  • 人生模式 - 如何跟潜意识对话
  • ubuntu18.04安装redis
  • 02 LaTeX文字实战应用
  • Flash:Flash动画设计软件界面的简介、Flash AS 3.0代码编程入门教程之详细攻略
  • C语言进阶——自定义类型
  • php的引用
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • [LeetCode] Wiggle Sort
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • 4. 路由到控制器 - Laravel从零开始教程
  • axios 和 cookie 的那些事
  • C语言笔记(第一章:C语言编程)
  • HashMap ConcurrentHashMap
  • JS变量作用域
  • JS学习笔记——闭包
  • Linux链接文件
  • Rancher如何对接Ceph-RBD块存储
  • Sass Day-01
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Windows Containers 大冒险: 容器网络
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 基于遗传算法的优化问题求解
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 通信类
  • 新书推荐|Windows黑客编程技术详解
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • #NOIP 2014#Day.2 T3 解方程
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (10)STL算法之搜索(二) 二分查找
  • (Java数据结构)ArrayList
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (力扣题库)跳跃游戏II(c++)
  • (五)c52学习之旅-静态数码管
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • *Django中的Ajax 纯js的书写样式1
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .form文件_SSM框架文件上传篇
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .NET委托:一个关于C#的睡前故事
  • .Net中的设计模式——Factory Method模式
  • /deep/和 >>>以及 ::v-deep 三者的区别
  • @Bean, @Component, @Configuration简析
  • [ Linux Audio 篇 ] 音频开发入门基础知识