百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

JavaScript错误处理完整指南_js运行时错误

liebian365 2025-02-20 16:39 3 浏览 0 评论

大家好,我是 Echa。

本文将带你了解 JavaScript 中常见的错误类型,处理同步和异步 JavaScript/Node.js 代码中错误和异常的方式,以及错误处理最佳实践!

1. 错误概述

JavaScript 中的错误是一个对象,在发生错误时会抛出该对象以停止程序。在 JavaScript 中,可以通过构造函数来创建一个新的通用错误:

const err = new Error("Error");

当然,也可以省略 new 关键字:

const err = Error("Error");

Error 对象有三个属性:

  • message:带有错误消息的字符串;
  • name: 错误的类型;
  • stack:函数执行的堆栈跟踪。

例如,创建一个 TypeError 对象,该消息将携带实际的错误字符串,其 name 将是“TypeError”:

const wrongType = TypeError("Expected number");

wrongType.message; // 'Expected number'
wrongType.name;    // 'TypeError'

堆栈跟踪是发生异常或警告等事件时程序所处的方法调用列表:

它首先会打印错误名称和消息,然后是被调用的方法列表。每个方法调用都说明其源代码的位置和调用它的行。可以使用此数据来浏览代码库并确定导致错误的代码段。此方法列表以堆叠的方式排列。它显示了异常首先被抛出的位置以及它如何通过堆栈方法调用传播。为异常实施捕获不会让它通过堆栈向上传播并使程序崩溃。

对于 Error 对象,Firefox 还实现了一些非标准属性:

  • columnNumber:错误所在行的列号;
  • filename:发生错误的文件
  • lineNumber:发生错误的行号

2. 错误类型

JavaScript 中有一系列预定义的错误类型。只要使用者没有明确处理应用程序中的错误,它们就会由 JavaScript 运行时自动选择和定义。

JavaScript中的错误类型包括:

  • EvalError
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

这些错误类型都是实际的构造函数,旨在返回一个新的错误对象。最常见的就是 TypeError。大多数时候,大部分错误将直接来自 JavaScript 引擎,例如 InternalError 或 SyntaxError。

JavaScript 提供了 instanceof 运算符可以用于区分异常类型:

try {
  If (typeof x !== ‘number’) {
       throw new TypeError(‘x 应是数字’);
  } else if (x <= 0) {
       throw new RangeError('x 应大于 0');
  } else {
       // ...
  }
} catch (err) {
    if (err instanceof TypeError) {
      // 处理 TypeError 错误
    } else if (err instanceof RangeError) {
      // 处理 RangeError 错误
  } else {
      // 处理其他类型错误
  }
}

下面来了解 JavaScript 中最常见的错误类型,并了解它们发生的时间和原因。

(1)SyntaxError

SyntaxError 表示语法错误。这些错误是最容易修复的错误之一,因为它们表明代码语法中存在错误。由于 JavaScript 是一种解释而非编译的脚本语言,因此当应用程序执行包含错误的脚本时会抛出这些错误。在编译语言的情况下,此类错误在编译期间被识别。因此,在修复这些问题之前,不会创建应用程序二进制文件。

SyntaxError 发生的一些常见原因是:

  • 缺少引号
  • 缺少右括号
  • 大括号或其他字符对齐不当

(2)TypeError

TypeError 是 JavaScript 应用程序中最常见的错误之一,当某些值不是特定的预期类型时,就会产生此错误。

TypeError 发生的一些常见原因是:

  • 调用不是方法的对象。
  • 试图访问 null 或未定义对象的属性
  • 将字符串视为数字,反之亦然

(3)ReferenceError

ReferenceError 表示引用错误。当代码中的变量引用有问题时,会发生 ReferenceError。可能忘记在使用变量之前为其定义一个值,或者可能试图在代码中使用一个不可访问的变量。在任何情况下,通过堆栈跟踪都可以提供充足的信息来查找和修复有问题的变量引用。

ReferenceErrors 发生的一些常见原因如下:

  • 在变量名中输入错误。
  • 试图访问其作用域之外的块作用域变量。
  • 在加载之前从外部库引用全局变量。

(4)RangeError

RangeError 表示范围错误。当变量设置的值超出其合法值范围时,将抛出 RangeError。它通常发生在将值作为参数传递给函数时,并且给定值不在函数参数的范围内。当使用记录不完整的第三方库时,有时修复起来会很棘手,因为需要知道参数的可能值范围才能传递正确的值。

RangeError 发生的一些常见场景如下:

  • 试图通过 Array 构造函数创建非法长度的数组。
  • 将错误的值传递给数字方法,例如 toExponential()toPrecision()toFixed()等。
  • 将非法值传递给字符串函数,例如 normalize()

(5)URIError

URIError 表示 URI错误。当 URI 的编码和解码出现问题时,会抛出 URIError。JavaScript 中的 URI 操作函数包括:decodeURIdecodeURIComponent 等。如果使用了错误的参数(无效字符),就会抛出 URIError。

(6)EvalError

EvalError 表示 Eval 错误。当 eval() 函数调用发生错误时,会抛出 EvalError。不过,当前的 JavaScript 引擎或 ECMAScript 规范不再抛出此错误。但是,为了向后兼容,它仍然是存在的。

如果使用的是旧版本的 JavaScript,可能会遇到此错误。在任何情况下,最好调查在eval()函数调用中执行的代码是否有任何异常。

(7)InternalError

InternalError 表示内部错误。在 JavaScript 运行时引擎发生异常时使用。它表示代码可能存在问题也可能不存在问题。

InternalError 通常只发生在两种情况下:

  • 当 JavaScript 运行时的补丁或更新带有引发异常的错误时(这种情况很少发生);
  • 当代码包含对于 JavaScript 引擎而言太大的实体时(例如,数组初始值设定项太大、递归太多)。

解决此错误最合适的方法就是通过错误消息确定原因,并在可能的情况下重构应用逻辑,以消除 JavaScript 引擎上工作负载的突然激增。

注意: 现代 JavaScript 中不会抛出 EvalError 和 InternalError。

(8)创建自定义错误类型

虽然 JavaScript 提供了足够的错误类型类列表来涵盖大多数情况,但如果这些错误类型不能满足要求,还可以创建新的错误类型。这种灵活性的基础在于 JavaScript 允许使用 throw 命令抛出任何内容。

可以通过扩展 Error 类以创建自定义错误类:

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

可以通过以下方式使用它:

throw ValidationError("未找到该属性: name")

可以使用 instanceof 关键字识别它:

try {
    validateForm() // 抛出 ValidationError 的代码
} catch (e) {
    if (e instanceof ValidationError) {
      
    }
    else {
      
    }
}

3. 抛出错误

很多人认为错误和异常是一回事。实际上,Error 对象只有在被抛出时才会成为异常

在 JavaScript 中抛出异常,可以使用 throw 来抛出 Error 对象:

throw TypeError("Expected number");

或者:

throw new TypeError("Expected number");

来看一个简单的例子:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Expected string");
  }

  return string.toUpperCase();
}

在这里,我们检查函数参数是否为字符串。如果不是,就抛出异常。

从技术上讲,我们可以在 JavaScript 中抛出任何东西,而不仅仅是 Error 对象:

throw Symbol();
throw 33;
throw "Error!";
throw null;

但是,最好避免这样做:要抛出正确的 Error 对象,而不是原语

4. 抛出异常时会发生什么?

异常一旦抛出,就会在程序堆栈中冒泡,除非在某个地方被捕获。

来看下面的例子:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Expected string");
  }

  return string.toUpperCase();
}

toUppercase(4);

在浏览器或 Node.js 中运行此代码,程序将停止并抛出错误:

这里还显示了发生错误的确切行。这个错误就是一个堆栈跟踪,有助于跟踪代码中的问题。堆栈跟踪从下到上:

at toUppercase (:3:11)
at :9:1

toUppercase 函数在第 9 行调用,在第 3 行抛出错误。除了在浏览器的控制台中查看此堆栈跟踪之外,还可以在 Error 对象的 stack 属性上访问它。

介绍完这些关于错误的基础知识之后,下面来看看同步和异步 JavaScript 代码中的错误和异常处理。

5. 同步错误处理

(1)常规函数的错误处理

同步代码会按照代码编写顺序执行。让我们再看看前面的例子:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Expected string");
  }

  return string.toUpperCase();
}

toUppercase(4);

在这里,引擎调用并执行 toUppercase,这一切都是同步发生的。 要捕获由此类同步函数引发的异常,可以使用 try/catch/finally:

try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
} finally {
  // ...
}

通常,try 会处理正常的路径,或者可能进行的函数调用。catch 就会捕获实际的异常,它接收 Error 对象。而不管函数的结果如何,finally 语句都会运行:无论它失败还是成功,finally 中的代码都会运行。

(2)生成器函数的错误处理

JavaScript 中的生成器函数是一种特殊类型的函数。它可以随意暂停和恢复,除了在其内部范围和消费者之间提供双向通信通道。为了创建一个生成器函数,需要在 function 关键字后面加上一个 *

function* generate() {
//
}

只要进入函数,就可以使用 yield 来返回值:

function* generate() {
  yield 33;
  yield 99;
}

生成器函数的返回值是一个迭代器对象。要从生成器中提取值,可以使用两种方法:

  • 在迭代器对象上调用 next()
  • 使用 for...of 进行迭代

以上面的代码为例,要从生成器中获取值,可以这样做:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

当我们调用生成器函数时,这里的 go 就是生成的迭代器对象。接下来,就可以调用 go.next() 来继续执行:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99

生成器也可以接受来自调用者的值和异常。除了 next(),从生成器返回的迭代器对象还有一个 throw() 方法。使用这种方法,就可以通过向生成器中注入异常来停止程序:

function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33

go.throw(Error("Tired of iterating!"));

const secondStep = go.next().value; // never reached

要捕获此类错误,可以使用 try/catch 将代码包装在生成器中:

function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}

生成器函数也可以向外部抛出异常。 捕获这些异常的机制与捕获同步异常的机制相同:try/catch/finally。

下面是使用 for...of 从外部使用的生成器函数的示例:

function* generate() {
  yield 33;
  yield 99;
  throw Error("Tired of iterating!");
}

try {
  for (const value of generate()) {
    console.log(value);
  }
} catch (error) {
  console.error(error.message);
}

输出结果如下:

这里,try 块中包含正常的迭代。如果发生任何异常,就会用 catch 捕获它。

6. 异步错误处理

浏览器中的异步包括定时器、事件、Promise 等。异步世界中的错误处理与同步世界中的处理不同。下面来看一些例子。

(1)定时器的错误处理

上面我们介绍了如何使用 try/catch/finally 来处理错误,那异步中可以使用这些来处理错误吗?先来看一个例子:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Wrong!");
  }, 1000);
}

此函数在大约 1 秒后会抛出错误。那处理此异常的正确方法是什么?以下代码是无效的:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Wrong!");
  }, 1000);
}

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

我们知道,try/catch是同步的,所以没办法这样来处理异步中的错误。当传递给 setTimeout的回调运行时,try/catch 早已执行完毕。程序将会崩溃,因为未能捕获异常。它们是在两条路径上执行的:

A: --> try/catch
B: --> setTimeout --> callback --> throw

(2)事件的错误处理

我们可以监听页面中任何 HTML 元素的事件,DOM 事件的错误处理机制遵循与任何异步 Web API 相同的方案。

来看下面的例子:

const button = document.querySelector("button");

button.addEventListener("click", function() {
  throw Error("error");
});

这里,在单击按钮后立即抛出了异常,我们该如何捕获这个异常呢?这样写是不起作用的,也不会阻止程序崩溃:

const button = document.querySelector("button");

try {
  button.addEventListener("click", function() {
    throw Error("error");
  });
} catch (error) {
  console.error(error.message);
}

与前面的 setTimeout 例子一样,任何传递给 addEventListener 的回调都是异步执行的:

Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw

如果不想让程序崩溃,为了正确处理错误,就必须将 try/catch 放到 addEventListener 的回调中。不过这样做并不是最佳的处理方式,与 setTimeout 一样,异步代码路径抛出的异常无法从外部捕获,并且会使程序崩溃。

下面会介绍 Promises 和 async/await 是如何简化异步代码的错误处理的。

(3)onerror

HTML 元素有许多事件处理程序,例如 onclickonmouseenteronchange 等。除此之外,还有 onerror,每当 标签或

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: