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

JavaScript错误处理完全指南:从基础到自定义错误实战

JavaScript错误处理手册

错误和异常在应用程序开发中是不可避免的。作为程序员,我们有责任优雅地处理这些错误,以免影响应用程序的用户体验。正确处理错误还有助于程序员调试和理解错误原因,从而更好地解决问题。

JavaScript作为一种流行的编程语言已经超过三十年。我们使用JavaScript和各种基于JavaScript的流行库(如ReactJS)及框架(如Next.js、Remix等)构建Web、移动端、PWA和服务器端应用程序。

作为一种弱类型语言,JavaScript带来了正确处理类型安全的挑战。TypeScript有助于管理类型,但我们仍然需要在代码中高效处理运行时错误。

如果您使用JavaScript开发有一段时间了,可能对TypeError、RangeError、ReferenceError等错误相当熟悉。所有这些错误都可能导致无效数据、不良的页面转换、不需要的结果,甚至整个应用程序崩溃——这些都不会让最终用户满意!

在本手册中,您将学习关于JavaScript错误处理的一切知识。我们将从理解错误、错误类型和发生情况开始。然后您将学习如何处理这些错误,以免它们导致糟糕的用户体验。最后,您还将学习构建自己的自定义错误类型和清理方法,以更好地处理代码流程以进行优化和性能提升。

希望您喜欢阅读并练习我在文章末尾提供的任务。

本手册也可作为视频会话提供,作为"40天JavaScript挑战"计划的一部分。请查看。

目录

  • JavaScript中的错误
  • 使用try和catch处理错误
    • try块
    • catch块
  • 错误处理:实际用例
    • 处理除以零
    • 处理JSON
  • 错误对象的剖析
    • 什么是错误对象?
  • 抛出错误和重新抛出错误
    • 重新抛出
  • 使用finally与try-catch
    • 使用finally的注意事项
  • 自定义错误
    • 自定义错误的实际用例
  • 给您的任务分配
    • 找出输出结果
    • 支付流程验证
  • 40天JavaScript挑战计划
  • 结束之前...

JavaScript中的错误

错误和异常是破坏程序执行的事件。JavaScript逐行解析和执行代码。源代码会根据编程语言的语法进行评估,以确保其有效和可执行。如果存在不匹配,JavaScript会遇到解析错误。您需要确保遵循正确的语言语法和语法规则,以避免解析错误。

看看下面的代码片段。这里,我们犯了没有关闭console.log括号的错误。

console.log("hi"

这将导致如下语法错误:

Uncaught SyntaxError: missing ) after argument list

其他类型的错误可能由于错误的数据输入、尝试读取不存在的值或属性,或者对不准确的数据进行操作而发生。让我们看一些例子:

console.log(x); // Uncaught ReferenceError: x is not definedlet obj = null;
console.log(obj.name); // Uncaught TypeError: Cannot read properties of nulllet arr = new Array(-1) // Uncaught RangeError: Invalid array lengthdecodeURIComponent("%"); // Uncaught URIError: URI malformedeval("var x = ;"); // Uncaught EvalError

以下是您可能遇到的可能运行时错误列表及其描述:

  • ReferenceError - 当尝试访问未定义的变量时发生
  • TypeError - 当对错误类型的值执行操作时发生
  • RangeError - 当值超出允许范围时发生
  • SyntaxError - 当JavaScript代码语法有错误时发生
  • URIError - 当在编码和解码URI时使用不正确的URI函数时发生
  • EvalError - 当eval()函数出现问题时发生
  • InternalError - 当JavaScript引擎遇到内部限制(堆栈溢出)时发生
  • AggregateError - 在ES2021中引入,用于同时处理多个错误
  • 自定义错误 - 这些是用户定义的错误,我们很快将学习如何创建和使用它们

您是否注意到我们上面使用的所有代码示例都会产生一条解释错误内容的消息?如果您仔细查看这些消息,会发现一个名为"Uncaught"的词。这意味着错误发生了,但没有被捕获和管理。这正是我们现在要解决的问题——让您知道如何处理这些错误。

使用try和catch处理错误

JavaScript应用程序可能因各种原因而崩溃,如无效语法、无效数据、缺少API响应、用户错误等。这些原因大多数可能导致应用程序崩溃,您的用户将看到一个空白的白页。

您可以使用try...catch优雅地处理这些情况,而不是让应用程序崩溃。

try {// 逻辑或代码
} catch (err) {// 处理错误
}

try块

try块包含可能抛出错误的代码——业务逻辑。开发人员总是希望他们的代码没有错误。但同时,您应该意识到代码可能因多种原因抛出错误,例如:

  • 解析JSON
  • 运行API逻辑
  • 访问嵌套对象属性
  • DOM操作
  • 以及更多

当try块中的代码抛出错误时,try块中剩余代码的执行将被暂停,控制权转移到最近的catch块。如果没有发生错误,则完全跳过catch块。

try {// 可能抛出错误的代码
} catch (error) {// 在此处理错误
}

catch块

catch块仅在try块中抛出错误时运行。它接收Error对象作为参数,为我们提供有关错误的更多信息。在下面显示的示例中,我们使用了名为abc的东西而没有声明它。JavaScript将抛出如下错误:

try {console.log("execution starts here");abc;console.log("execution ends here");
} catch (err) {console.error("An Error has occured", err);
}

JavaScript逐行执行代码。上述代码的执行顺序将是:

  1. 首先,字符串"execution starts here"将被记录到控制台
  2. 然后控制权将移动到下一行并在那里找到abc。它是什么?JavaScript在任何地方都找不到它的定义。是时候发出警报并抛出错误了。控制权不会移动到下一行(下一个console log),而是移动到catch块
  3. 在catch块中,我们通过将错误记录到控制台来处理错误。我们可以做许多其他事情,如显示toast消息、向用户发送电子邮件或关闭烤面包机(如果您的客户需要)

如果没有try...catch,错误将使应用程序崩溃。

错误处理:实际用例

现在让我们看看使用try...catch进行错误处理的一些实际用例。

处理除以零

这是一个将一个数除以另一个数的函数。因此,我们有两个数字的参数传递给函数。我们希望确保除法永远不会遇到除以零(0)的错误。

作为主动措施,我们编写了一个条件:如果除数为零,我们将抛出一个错误,说明不允许除以零。在所有其他情况下,我们将进行除法运算。如果发生错误,catch块将处理错误并执行需要的操作(在这种情况下,将错误记录到控制台)。

function divideNumbers(a, b) {try {if (b === 0) {const err = new Error("Division by zero is not allowed.");throw err;}const result = a/b;console.log(`The result is ${result}`);} catch(error) {console.error("Got a Math Error:", error.message)}
}

现在,如果我们使用以下参数调用函数,我们将得到结果5,第二个参数是非零值。

divideNumbers(15, 3); // 结果是5

但是如果我们为第二个参数传递0值,程序将抛出错误,并将其记录到控制台。

divideNumbers(15, 0);

输出:

Got a Math Error: Division by zero is not allowed.

处理JSON

通常,您会获得JSON作为API调用的响应。您需要在JavaScript代码中解析此JSON以提取值。如果API错误地发送了一些格式错误的JSON怎么办?您不能让用户界面因此崩溃。您需要优雅地处理它——这时try...catch块再次来救援:

function parseJSONSafely(str) {try {return JSON.parse(str);} catch (err) {console.error("Invalid JSON:", err.message);return null;}
}const userData = parseJSONSafely('{"name": "tapaScript"}'); // 已解析
const badData = parseJSONSafely('name: tapaScript');         // 优雅处理

如果没有try...catch,第二次调用将使应用程序崩溃。

错误对象的剖析

在编程中遇到错误可能是一种可怕的感觉。但JavaScript中的错误不仅仅是一些可怕、烦人的消息——它们是结构化的对象,携带了大量有关出错原因、位置和方式的有用信息。

作为开发人员,我们需要理解Error对象的解剖结构,以帮助我们在生产级应用程序问题中更快地调试和更智能地恢复。

让我们通过示例深入探讨Error对象、其属性以及如何有效使用它。

什么是错误对象?

当运行时出现问题时,JavaScript引擎会抛出一个Error对象。此对象包含有用的信息,如:

  • 错误消息:这是人类可读的错误消息
  • 错误类型:TypeError、ReferenceError、SyntaxError等,我们上面讨论过
  • 堆栈跟踪:这帮助您导航到错误的根源。它是一个包含错误抛出点堆栈跟踪的字符串

让我们看看下面的代码片段。JavaScript引擎将在此代码中抛出错误,因为变量y未定义。错误对象包含错误名称(类型)、消息和堆栈跟踪信息。

function doSomething() {const x = y + 1; // y未定义
}
try {doSomething();
} catch (err) {console.log(err.name);    // ReferenceErrorconsole.log(err.message); // y未定义console.log(err.stack); // ReferenceError: y未定义// at doSomething (<anonymous>:2:13)// at <anonymous>:5:3
}

提示:如果您需要错误对象中的任何特定属性,可以使用解构来实现。这是一个示例,我们只对错误名称和消息感兴趣,而不是堆栈。

try {JSON.parse("{ invalid json }");
} catch ({name, message}) {console.log("Name:", name);       // 名称: SyntaxErrorconsole.log("Message:", message); // 消息: Expected property name or '}' in JSON at position 2 (line 1 column 3)
}

抛出错误和重新抛出错误

JavaScript提供了一个throw语句来手动触发错误。当您想在代码中处理无效条件时,这非常有用(还记得除以零的问题吗?)。

要抛出错误,您需要创建一个带有详细信息的Error对象实例,然后抛出它。

throw new Error("Something is bad!");

当代码执行遇到throw语句时:

  • 它立即停止当前代码块的执行
  • 控制权转移到最近的catch块(如果有)
  • 如果未找到catch块,错误将不会被捕获。错误会冒泡,并可能最终使程序崩溃。您可以从这里更深入地了解事件和事件冒泡。

重新抛出

有时,在catch块中捕获错误本身并不足够。有时,您可能不知道如何完全处理错误,并且可能想做其他事情,例如:

  • 向错误添加上下文
  • 将错误记录到基于文件的记录器中
  • 将错误传递给更专业的人处理

这就是重新抛出的用武之地。通过重新抛出,您可以捕获错误,对其进行其他操作,然后再次抛出它。

function processData() {try {parseUserData();} catch (err) {console.error("Error in processData:", err.message);throw err; // 重新抛出,以便外部函数也可以处理它}
}function main() {try {processData();} catch (err) {handleErrorBetter(err);}
}

在上面的代码中,processData()函数捕获错误,记录它,然后再次抛出它。外部main()函数现在可以捕获它并做更多事情来更好地处理它。

在真实的应用程序开发中,您会希望分离错误的关注点,例如:

  • API层 - 在此层中,您可以检测HTTP失败

    async function fetchUser(id) {const res = await fetch(`/users/${id}`);if (!res.ok) throw new Error("User not found"); // 在此抛出return res.json();
    }
    
  • 服务层 - 在此层中,您处理业务逻辑。因此将处理无效条件的错误。

    async function getUser(id) {try {const user = await fetchUser(id);return user;} catch (err) {console.error("Fetching user failed:", err.message);throw new Error("Unable to load user profile"); // 重新抛出}
    }
    
  • UI层 - 显示用户友好的错误消息。

    async function showUserProfile() {try {const user = await getUser(123);renderUser(user);} catch (err) {displayError(err.message); // 向用户显示适当的消息}
    }
    

使用finally与try-catch

try...catch块为我们提供了一种优雅处理错误的方法。但您可能总是希望执行一些代码,无论是否发生错误。例如,关闭数据库连接、停止加载器、重置某些状态等。这就是finally的用武之地。

try {// 可能抛出错误的代码
} catch (error) {// 处理错误
} finally {// 总是运行,无论是否发生错误
}

让我们看一个例子:

function performTask() {try {console.log("Doing something cool...");throw new Error("Oops!");} catch (err) {console.error("Caught error:", err.message);} finally {console.log("Cleanup: Task finished (success or fail).");}
}performTask();

在performTask()函数中,错误在第一个console log之后抛出。因此,控制权将移动到catch块并记录错误。之后,finally块将执行其console log。

输出:

Doing something cool...
Caught error: Oops!
Cleanup: Task finished (success or fail).

让我们看一个更实际的API调用和处理加载微调器的用例:

async function loadUserData() {showSpinner(); // 在此显示微调器try {const res = await fetch('/api/user');const data = await res.json();displayUser(data);} catch (err) {showError("Failed to load user.");} finally {hideSpinner(); // 在成功和失败情况下都隐藏微调器}
}

通常,在从浏览器进行API(异步)调用时,我们会显示一个加载微调器。无论API调用是成功响应还是错误,我们都必须停止显示加载微调器。您可以在finally块中执行此操作,而不是两次执行停止微调器的代码逻辑(一次在try块内部,然后在catch块内部再次执行)。

使用finally的注意事项

finally块可以覆盖返回值或抛出的错误。这种行为可能会令人困惑,并可能导致错误。

function test() {try {return 'from try';} finally {return 'from finally';}
}console.log(test());

您认为上面的代码返回什么?
它将返回'from finally'。return 'from try'被完全忽略。finally中的返回默默地覆盖了它。

让我们再看一个相同问题的例子:

function willThrow() {try {throw new Error("Original Error");} finally {throw new Error("Overriding Error"); // 原始错误丢失}
}try {willThrow();
} catch (err) {console.log(err.message); // "Overriding Error"
}

这里,原始错误("Original Error")被吞没了。finally块覆盖了实际的根本原因。

使用finally时:

  • 尽可能避免从finally返回和抛出
  • 避免在finally块中执行可能影响实际结果的逻辑。try块是最适合的地方
  • 必须避免在finally块内进行任何关键决策
  • 使用finally进行清理活动,例如关闭文件、连接和停止加载微调器等
  • 确保finally块包含无副作用代码

自定义错误

在复杂的应用程序中,使用通用Error及其现有类型(如ReferenceError、SyntaxError等)可能有点模糊。JavaScript允许您创建与业务用例更相关的自定义错误。自定义错误可以提供:

  • 有关错误的额外上下文信息
  • 错误的清晰度
  • 更可读的日志
  • 有条件处理多个错误情况的能力

JavaScript中的自定义错误是用户定义的错误类型,扩展了内置的Error类。自定义错误应该是扩展JavaScript Error类的ES6类。我们可以在构造函数中使用super()来继承Error类的message属性。您可以选择为自定义错误分配名称并清理堆栈跟踪。

class MyCustomError extends Error {constructor(message) {super(message);         // 继承message属性this.name = this.constructor.name; // 可选但推荐Error.captureStackTrace(this, this.constructor); // 清理堆栈跟踪}
}

现在让我们看一个自定义错误的实际用例。

自定义错误的实际用例

在网页上使用表单是一个极其常见的用例。表单可能包含一个或多个输入字段。建议在处理表单数据进行任何服务器操作之前验证用户输入。

让我们创建一个自定义验证错误,可用于验证多个表单输入数据,如用户的电子邮件、年龄、电话号码等。

首先,我们将创建一个名为ValidationError的类,它扩展了Error类。构造函数使用错误消息设置ValidationError类。我们还可以实例化其他属性,如name、field等。

class ValidationError extends Error {constructor(field, message) {super(`${field}: ${message}`);this.name = "ValidationError";this.field = field;}
}

现在,让我们看看如何使用ValidationError。我们可以验证用户模型以检查其属性,并在期望不匹配时抛出ValidationError。

function validateUser(user) {if (!user.email.includes("@")) {throw new ValidationError("email", "Invalid email format");}if (!user.age || user.age < 18) {throw new ValidationError("age", "User must be 18+");}
}

在上面的代码片段中:

  • 如果用户的电子邮件不包含@符号,我们抛出无效电子邮件格式验证错误
  • 如果用户的年龄信息缺失或低于18岁,我们抛出另一个验证错误

自定义错误使我们能够创建特定于领域/用途的错误类型,使代码更易于管理且更少出错。

给您的任务分配

如果您已经阅读了本手册至此,我希望您现在对JavaScript错误处理有了扎实的理解。让我们尝试一些基于我们所学知识的任务。这将很有趣。

找出输出结果

以下代码片段的输出是什么?为什么?

try {let r = p + 50;console.log(r);
} catch (error) {console.log("An error occurred:", error.name);
}

选项是:

  • ReferenceError
  • SyntaxError
  • TypeError
  • 没有错误,它打印10

支付流程验证

编写一个函数processPayment(amount),验证金额是否为正数且不超过余额。如果任何条件失败,抛出适当的错误。

提示:您可以考虑在此处创建自定义错误。

40天JavaScript挑战计划

学习某事有101种方法。但没有什么能比得上结构化和渐进式的学习方法。在软件工程领域工作了二十多年后,我能够收集最好的JavaScript内容来创建40天JavaScript挑战计划。

如果您想通过基本概念、项目和任务免费(永远)学习JavaScript,请查看。专注于JavaScript的基础知识将为您在Web开发的未来做好充分准备。

结束之前...

就这样!希望您觉得这篇文章有见地。

让我们联系:

  • 订阅我的YouTube频道
  • 在LinkedIn上关注,如果您不想错过每日的技能提升技巧
  • 查看并关注我在GitHub上的开源工作

很快再见我的下一篇文章。在那之前,请照顾好自己并继续学习。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
公众号二维码

http://www.hskmm.com/?act=detail&tid=10406

相关文章:

  • 1、论文准备
  • PION 游击
  • Web3 开发者修炼全图谱:从 Web2 走向 Web3 的实用的系统性学习指南
  • 实用指南:医院高值耗材智能化管理路径分析(下)
  • Flutter应用自动更新系统:生产环境的挑战与解决方案
  • .NET Core中使用SignalR
  • 实用指南:修复Conda连接异常:CondaHTTPError HTTP 000 CONNECTION FAILED故障排除指南
  • 高级数据结构手册
  • Tarjan 学习笔记
  • 使用JavaScript和CSS创建动态高亮导航栏
  • Gridspech 全通关
  • 1967
  • 20253320蒋丰任
  • .
  • 又有两位智驾大牛联手入局具身智能机器人赛道创业,已完成数亿元融资!
  • 纯国产GPU性能对比,谁才是国产算力之王?
  • 地平线明年发布并争取量产舱驾一体芯片;比亚迪补强智舱团队,斑马智行原 CTO 加入
  • 英伟达入股英特尔,当竞争对手便成协作者,真正受益的......
  • ODT/珂朵莉树 入门
  • 在AI技术快速实现功能的时代,挖掘新需求成为关键突破点——某知名游戏资源分析工具需求洞察
  • 蜜罐
  • 【光照】[漫反射]UnityURP兰伯特有光照衰减吗?
  • prenotami.esteri.it 意大利签证预约error
  • reLeetCode 热题 100- 15. 三数之和 - MKT
  • XXL-TOOL v2.1.0 发布 | Java工具类库
  • Python-Pathlib库
  • 反省
  • [Nacos/Docker/MCP] Nacos 3.x : 为 AI MCP 而生
  • 牛客周赛 Round 108 CDEF题解
  • Redis的使用问题