关于 JavaScript 错误处理的最完整指南(上半部)

开发 前端
我们的开发过程中并不总是一帆风顺。特别是在某些情况下,我们可能希望停止程序或在发生不良情况时通知用户。

什么是编程中的错误

我们的开发过程中并不总是一帆风顺。特别是在某些情况下,我们可能希望停止程序或在发生不良情况时通知用户。

[[341895]]

例如:

  • 程序试图打开一个不存在的文件、
  • 网络连接断开
  • 用户输入了无效字符

在类似这些情况下,我们可以自己写个自定义的错误来管理,或者直接让引擎为我们去定义这些错误。有了错误定义后,我们可以用消息通知用户,或者停止执行程序的运行。

JavaScript 中的错误是什么

JavaScript中的错误是一个对象。要在 JS 创建一个错误,可以使用 Error 对象,如下所示:

  1. const err = new Error('霍霍,好像哪里出问题了!') 

也可以省略new关键字:

  1. const err = Error('霍霍,好像哪里出问题了!') 

创建,错误对象有三个属性:

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

例如,我们使用 TypeError 对象创建一个错误,对应的 message 是创建的传入的字符号,name 是 "TypeError"

  1. const wrongType = TypeError("霍霍,好像哪里出问题了!") 
  2. wrongType.message // "霍霍,好像哪里出问题了!" 
  3. wrongType.name // "TypeError" 

JavaScript中的许多类型的错误

JavaScript 中有很多类型的错误 ,如:

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

记住,所有这些错误类型都是实际的构造函数,意味着返回一个新的错误对象。

在我们的代码中,主要还是使用Error和TypeError这两种最常见的类型来创建自己的错误对象 。

大多数时候,大多数错误将直接来自JavaScript引擎,例如InternalError或SyntaxError。

如果你重新赋值给 const 声明的变量时,就会引发 TypeError 错误。

  1. const name = "前端小智" 
  2. name = "王大冶" 
  3. // // TypeError: Assignment to constant variable. 

SyntaxError 错误一般是关键字打错了,如下所示:

  1. va x = '33'
  2. // SyntaxError: Unexpected identifier 

或者,当在错误的地方使关键字时,例如await 和 async 的使用:

  1. function wrong(){ 
  2.     await 99; 
  3. }wrong();// SyntaxError: await is only valid in async function 

另一个TypeError的例子是,在页面操作不存在的 DOM 元素。

  1. Uncaught TypeError: button is null 

除了这些内置错误外,在浏览器中还有:

  • DOMException
  • DOMError,现在已经废弃,不再使用了。

DOMException是与 Web API 相关的一系列错误。当我们在浏览器中执行愚蠢的操作时,它们会被抛出,例如:

  1. document.body.appendChild(document.cloneNode(true)); 

结果:

  1. Uncaught DOMException: Node.appendChild: May not add a Document as a child 

什么是异常?

大多数开发人员认为错误和异常是一回事。实际上,错误对象只有在抛出时才会变成异常。

要在JavaScript中引发异常,我们使用throw 关键字把错误抛出去:

  1. const wrongType = TypeError("霍霍,好像哪里出问题了!") 
  2. throw wrongType; 

简写形式:

  1. throw TypeError("霍霍,好像哪里出问题了!") 

或者

  1. throw new TypeError("霍霍,好像哪里出问题了!") 

在函数体或者条件之外抛出异步的可能性不大,考虑下面的例子:

  1. function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("霍霍,好像哪里出问题了!"); 
  4.   }  return string.toUpperCase(); 

这里我们检查函数参数是否为字符串。如果不是,我们抛出一个异常。从技术上讲,JavaScript中可以抛出任何东西,而不仅仅是错误对象

  1. throw Symbol(); 
  2. throw 33; 
  3. throw "Error!"; 
  4. throw null; 

但是,最好避免这些事情:始终抛出正确的错误对象,而不是一些基本类型。

这样有助于在代码中,错误处理的一致性。其他成员可以期望在错误对象上访问error.message或error.stack 来知道错误的源头。

当我们抛出异常时会发生什么?

异常就像一个上升的电梯:一旦你抛出一个,它就会在程序堆栈中冒泡,除非它在某个地方被捕获。

考虑以下代码:

  1. function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("参数类型需要是 string 的"); 
  4.   }  return string.toUpperCase(); 
  5. }toUppercase(4); 

运行代码会在控制台看到:

  1. Uncaught TypeError: Wrong type given, expected a string 
  2.     toUppercase http://localhost:5000/index.js:3 
  3.     <anonymous> http://localhost:5000/index.js:9 

可以看到发生错误的确切行。

这个报告是一个堆栈跟踪,它有助于跟踪代码中的问题。堆栈跟踪从下至上:

  1. toUppercase http://localhost:5000/index.js:3 
  2.    <anonymous> http://localhost:5000/index.js:9 

除了在浏览器的控制台中看到此堆栈跟踪外,还可以通过错误对象的stack属性进行查看。

如果异常未被捕获,也就是说,程序员不采取任何措施来捕获它,程序将崩溃。

何时何地捕获代码中的异常取决于特定的用例。

例如,我们可能想在堆栈中传递一个异常,以使程序完全崩溃。这种情况发生在, 让错误停止程序比处理无效数据来得更安全。

接下来,我们来看看 JavaScript 同步和异步中的错误和异常处理。

同步中的错误处理

同步代码在大多数情况下都很简单,因此它的错误处理也很简单。

常规函数的错误处理

同步代码的执行顺序与写入顺序相同。我们再看一下前面的例子:

  1. function toUppercase(string) { 
  2.   if (typeof string !== "string") { 
  3.     throw TypeError("参数类型需要是 string 的"); 
  4.   }  return string.toUpperCase(); 
  5. }toUppercase(4); 

在这里,引擎调用并执行toUppercase。所有这些都是同步发生的。要捕获同步函数引发的异常,我们可以使用try/catch/finally:

  1. try { 
  2.   toUppercase(4); 
  3. } catch (error) { 
  4.   console.error(error.message); 
  5. } finally { 

try/catch/finally是一个同步结构,但它也可以捕获异步出现的异常。

使用 generator 函数来处理错误

JavaScript中的生成器函数是一种特殊的函数。除了在其内部作用域和使用者之间提供双向通信通道之外,还可以随意暂停和恢复。

要创建一个生成器函数,我们在function关键字后面放一个*:

  1. function* generate() { 
  2.   // 

在函数内可以使用yield返回值:

  1. function* generate() { 
  2.   yield 33; 
  3.   yield 99; 

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

  • 使用 next() 方法
  • 通过 for...of 遍历

如下所示,要想在生成器中获取值,我们可以这样做:

  1. function* generate() { 
  2.   yield 33; 
  3.   yield 99; 
  4. }const go = generate(); 
  5. const firstStep = go.next().value; // 33 
  6. const secondStep = go.next().value; // 99 

成器也可以采用其他方法工作:它们可以接收调用者返回的值和异常。

除了next()之外,从生成器返回的迭代器对象还具有throw()方法。使用这种方法,我们可以通过向生成器中注入一个异常来停止程序:

  1. function* generate() { 
  2.   yield 33; 
  3.   yield 99; 
  4. }const go = generate(); 
  5. const firstStep = go.next().value; // 33 
  6. go.throw(Error("我要结束你!")); 
  7. const secondStep = go.next().value; // 这里会抛出异常 

要获取此错误,可以在生成器函数中使用 try/catch/finally:

  1. function* generate() { 
  2.   try { 
  3.     yield 33; 
  4.     yield 99; 
  5.   } catch (error) { 
  6.     console.error(error.message); 
  7.   }} 

下面这个事例是使用 for...of 来获取 生成器函数中的值:

  1. function* generate() { 
  2.   yield 33; 
  3.   yield 99; 
  4.     throw Error("我要结束你!") 
  5. }try { 
  6.   for (const value of generate()) { 
  7.     console.log(value) 
  8.   }} catch (error) { 
  9.   console.log(error.message) 
  10. }/* 输出: 
  11.   33 
  12.   99 
  13.   我要结束你! 
  14. */ 

异步中的错误处理

JavaScript本质上是同步的,是一种单线程语言。

诸如浏览器引擎之类的宿主环境使用许多Web API, 增强了 JS 以与外部系统进行交互并处理与 I/O 绑定的操作。

浏览器中异步操作有:定时器相关的函数、事件和 Promise。

异步中的错误处理不同于同步的错误处理。我们来看一些例子。

定时器的错误处理

考虑下面的代码片段:

  1. function failAfterOneSecond() { 
  2.   setTimeout(() => { 
  3.     throw Error("Something went wrong!"); 
  4.   }, 1000); 

这个函数大约在1秒后抛出异常,处理这个异常的正确方法是什么?

下面的方法不起作用:

  1. function failAfterOneSecond() { 
  2.   setTimeout(() => { 
  3.     throw Error("Something went wrong!"); 
  4.   }, 1000); 
  5. }try { 
  6.   failAfterOneSecond();} catch (error) { 
  7.   console.error(error.message); 

我们知道 try/catch 是同步,而 setTimeout 是异步的。当执行到 setTimeout回调时,try/catch 早已跑完了,所以异常就无法捕获到。

它们在两务不同的轨道上:

  1. Track A: --> try/catch 
  2. Track B: --> setTimeout --> callback --> throw 

如果能让程序跑下去,把 try/catch 移动到 setTimeout 里面。但这种做法意义不大,后面我们会使用 Promise 来解决这类的问题。

事件中错误处理

DOM 的事件操作(监听和触发),都定义在EventTarget接口。Element节点、document节点和window对象,都部署了这个接口。此外,XMLHttpRequest、AudioNode、AudioContext等浏览器内置对象,也部署了这个接口。该接口就是三个方法,addEventListener和removeEventListener用于绑定和移除监听函数,dispatchEvent用于触发事件。

DOM 事件的错误处理机制遵循任何异步Web API的相同方案。

考虑下面示例:

  1. const button = document.querySelector("button"); 
  2. button.addEventListener("click", function() { 
  3.   throw Error("Can't touch this button!"); 
  4. }); 

在这里,单击按钮后立即引发异常。我们如何抓住它?下面这种方式没啥作用,也不会阻止程序崩溃:

  1. const button = document.querySelector("button"); 
  2. try { 
  3.   button.addEventListener("click", function() { 
  4.     throw Error("Can't touch this button!"); 
  5.   });} catch (error) { 
  6.   console.error(error.message); 

与 setTimeout 一样,addEventListener 也是异步执行的。

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

如果能让程序跑下去,把 try/catch 移动到 addEventListener 里面。但这种做法意义不大,后面我们会使用 Promise 来解决这类的问题。

onerror 怎么样

HTML元素具有许多事件处理程序,例如onclick,onmouseenter,onchange等,当然还有 onerror。

当 img 标签或 script 标签遇到不存在的资源时,onerror事件处理程序都会触发。

考虑下面示例:

  1. ... 
  2. <body> 
  3.   <img src="nowhere-to-be-found.png" alt="So empty!"> 
  4. </body> 
  5. ... 

当文件不存在时,控制台就会报如下的错误:

  1. GET http://localhost:5000/nowhere-to-be-found.png 
  2. [HTTP/1.1 404 Not Found 3ms] 

在 JS 中,我们可以通过 onerror 来捕获这个错误:

  1. const image = document.querySelector("img"); 
  2. image.onerror = function(event) { 
  3.   console.log(event); 
  4. }; 

更好的方式:

  1. const image = document.querySelector("img"); 
  2. image.addEventListener("error", function(event) { 
  3.   console.log(event); 
  4. }); 

这种方式对于一些请求资源丢失的情况很有用,但 onerror 与 throw 与 try/cathc 无关。

本文转载自微信公众号「 大迁世界」,可以通过以下二维码关注。转载本文请联系****公众号。

 

责任编辑:赵宁宁 来源: 大迁世界
相关推荐

2020-09-15 08:28:17

JavaScript错误处理

2022-11-16 08:41:43

2017-03-08 08:57:04

JavaScript错误堆栈

2017-04-06 14:40:29

JavaScript错误处理堆栈追踪

2023-10-26 12:05:14

Golang开发

2020-08-20 10:16:56

Golang错误处理数据

2021-09-27 15:33:48

Go 开发技术

2021-04-14 07:08:14

Nodejs错误处理

2021-09-27 10:04:03

Go程序处理

2024-03-27 08:18:02

Spring映射HTML

2014-11-17 10:05:12

Go语言

2021-04-29 09:02:44

语言Go 处理

2021-05-11 10:01:54

avaScript错误处理

2023-12-26 22:05:53

并发代码goroutines

2015-10-09 13:54:14

切面编程错误处理机制

2023-10-28 16:30:19

Golang开发

2010-06-01 16:14:04

2009-08-05 16:04:50

2009-06-19 16:20:14

ASP.NET错误处理

2023-10-08 20:31:18

React
点赞
收藏

51CTO技术栈公众号