资讯专栏INFORMATION COLUMN

JavaScript 中的错误处理机制

AbnerMing / 679人阅读

摘要:错误处理在开发和调试过程中都显得尤为重要。跟全局函数有关的错误,在之后已经不再出现了内部错误。由引擎抛出的错误范围错误。事件任何没有的错误都会触发对象的事件。事件可以接收三个参数错误消息错误所在的和行号。

错误处理在开发和调试过程中都显得尤为重要。有些没有进行错误处理的应用,直接就将浏览器的错误展示给了用户,极大的降低了用户体验。比如有些很 low 的网站,打开某些页面就直接弹出 "object" 这样的错误,用户看到之后一脸懵逼,心想这是什么鬼?让人感觉极其的不专业。可见错误处理对一个应用来说是多么的重要。

这篇文章主要是给大家科普一些关于错误处理的知识,让大家在脑海中有一个概览。下一篇文章中我会结合具体的项目以及当前主流的一些框架,比如react, redux,来更深入的介绍如何运用这些框架去封装一整套错误处理的解决方案。

error 对象

Error 构造对象可以实例化一个 error 对象 (也就是Error 实例),而 error 对象就是一个包含了错误信息的对象。当代码解析或者运行时发生错误,javascript 引擎就会自动产生并抛出一个 error 对象, 然后程序就中断在发生错误的地方。

示例:

const error = new Error("Whoop!");
error.message; // Whoop!
error.name; // Error
error.stack; // "Error: Whoops! at :1:13"

我们常用的 messagename 都是 error 的标准属性,由于各个浏览器厂商对 error 进行了不同的扩展,所以在不同的浏览器中,error 也有不同的属性和方法, 非标准属性中我们常用的是 stack 属性(很多浏览器都扩展了这一属性), 它用来表示栈跟踪信息。

属性 含义
message 错误信息
name 错误类型
constructor 指定一个函数用来创建实例的原型,也就是指定构造器(创建自定义 Error 会用到)
stack (非标准) 栈跟踪信息
error 类型

除了普通的 Error 构造对象以外, javascript 还实现了其他几种主要的 error 构造对象1.

类型 解析 实例
EvalError eval错误。跟全局函数 eval() 有关的错误,在 ES5 之后已经不再出现了
InternalError 内部错误。由 JavaScript 引擎抛出的错误
RangeError 范围错误。发生在一个数值或参数超出其合法范围,主要包括超出数组范围或者超出数字取值范围 new Array(-1); (1234).toExponential(21);
ReferenceError 引用错误。通常是由于引用了一个不存在的值。 a.toString();
SyntaxError 语法错误。 a ? a+1;
TypeError 类型错误。通常是因为在执行特定的类型操作时,变量的类型不符合要求。例如变量中保存着意外类型,访问不存在的方法等。 var a = new {}; var a = {a:1}; a.reverse(); // 对象并没有 reverser 方法
URIError decodeURI() 或者 encodeURI() 传入非法参数时,也包括 encodeURIComponent() 和 decodeURIComponent() decodeURI("http://www.test.com&%"); encodeURIComponent("uD800");
encodeURI 和 encodeURIComponent 的区别

这里顺便说一下 encodeURIencodeURIComponent 的区别。已经了解的同学可以忽略这一小部分,继续往前面看。

encodeURI 是对统一资源标识符 (URI) 全部编码,而 encodeURIComponent 对统一资源标识符 (URI) 部分编码

假设一个 URI 是一个完整的 URI, 那么我们不必对那些在 URI 中保留的并且带有特殊含义的字符进行编码。由于 encodeURI 会替换掉所有字符,但是却不包含一些保留字符,如 "&", "+", "=" 等(这些字符在 GET 和 POST 请求中是特殊字符,需要被编码),所以 encodeURI 本身无法产生能使用与 HTTP GET 或者 POST 请求的 URI。但是我们可以使用 encodeURIComponent 来对这些字符进行编码。

encodeURIComponent 转义除了字母、数字、(、)、.、!、~、*、"、-和_之外的所有字符。
为了避免服务器收到不可预知的请求,对任何用户输入的作为 URI 部分的内容都需要用 encodeURIComponent 进行转义。
抛出和捕获错误 throw and try...catch

通常使用 throw 语句抛出错误,并用 try...catch 进行捕获。通常会把所有可能会抛出错误的代码都放在 try 语句块中,而把那些用于错误处理的代码放在 catch 块中。

throw 语句

throw 过程是阻塞的,程序会中断在第一个抛出错误的地方,所以后面的代码不会执行。

throw new SyntaxError("this is syntax error"); 
throw 123; // 不执行
throw "hi there"; // 不执行
throw true;  // 不执行
catch 语句

catch 代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去。

try {
  throw new Error("Whoops!");
} catch (e) {
  console.log(e.name + ":" + e.message);
}
console.log("hello!");

// Error:Whoops!
// hello!
finally 语句

finallytry...catch 中是可选的,但是一旦使用了它,它里面的代码就一定会被执行,也就是说不管 try 语句块中的代码是否正常执行,finnaly 都会被执行。正如下面的代码, 即使在 try 中资源被阻塞,由于我们在 finnaly 中执行了关闭操作,文件最后还是会被关闭。

openMyFile()
try {
   // 阻塞资源
   writeMyFile(theData);
} finally {
   closeMyFile(); // 始终会关闭资源
}

处理一个特定的错误。

    try {
      foo.bar();
    } catch (e) {
      switch (e.name) {
        case "RangeError":
          //do something
          console.log("RangeError: " + e.message);
          break;
        case "ReferenceError":
          //do something
          console.log("ReferenceError: " + e.message);
          break;
        default:
          console.log(e.name + ":" + e.message);
      }
    }
error 事件
任何没有 catch 的错误都会触发 window 对象的 error 事件。

error 事件可以接收三个参数:错误消息、错误所在的 URL 和行号。你可以通过以下两种方式给 window 绑上 error 事件2

  // message: 错误消息, source: 发生错误文件的 URL, lineno: 错误行号

  // 方法一
  window.onerror = function(messageOrEvent, source, lineno, colno, error) {
    alert(messageOrEvent, source, lineno, colno, error);
  }
  
  or 
  
  window.onerror = console.log;
  throw new Error("whoops!");
  
  // 方法二 
  window.addEventListener("error", function(errorEvent){
      alert(errorEvent.error);
  });

在实际情况中,error 事件并不常用(但是在某些情况下,如微信端的调试,error 事件还是挺有用的),因为我们还是希望所有的异常都能得到很好的处理,而不是把错误交给浏览器。但有的时候并不是所有的错误都能够被扑获,并且某些业务场景会使用到追踪浏览器报错的工具,这时候可能就需要将浏览器的错误抛出去,所以在这种情况下也需要去全局监听 error 事件。

自定义错误类型 Custom Error Types

创建一个自定义类 CustomError, 以方便去扩展更多的自定义 Error

CustomError.js

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === "function") {
      // 在浏览器领域,除了使用V8引擎的 Chrome,
      // 其它浏览器中不存在 Error.captureStackTrace()这一接口,
      // 所以在这里做一个条件判断。
      Error.captureStackTrace(this, this.constructor); // 返回调用堆栈信息, 用于在 error 对象上添加合理的 stack 属性。
    } else {
      this.stack = new Error(message).stack;
    }
  }
}
Error.captureStackTrace

Error.captureStackTrace 是用来在 targetObject 中添加一个 .stack 属性。对该属性进行访问时,将以字符串的形式返回 Error.captureStackTrace() 语句被调用时的代码位置信息(即:调用栈历史)。

Error.captureStackTrace(targetObject[, constructorOpt])

除了 targetObject, captureStackTrace 还接受一个类型为 function 的可选参数 constructorOpt,当传递该参数时,调用栈中所有 constructorOpt 函数之上的信息(包括 constructorOpt 函数自身),都会在访问 targetObject.stack 时被忽略。当需要对终端用户隐藏内部的技术细节时, constructorOpt 参数会很有用。

扩展自定义 Error 类型

通过基类 CustomError,我们可以创建出更多的自定义 Error, 比如下面的 HttpRequestErrorLoginExpiredError

HttpRequestError.js

class HttpRequestError extends CustomError {
  constructor(message, requestId, code, httpStatusCode) {
    const defaultAPIErrorMessage = () => {
      // 检查是否有网
      return window.navigator.onLine ? "Something wrong" : "No connection";
    };

    message = message || defaultAPIErrorMessage();
    super(message);

    this.requestId = requestId;
    this.code = code;
    this.httpStatusCode = httpStatusCode;
  }
}

throw new HttpRequestError(null, "requestId", "code", "httpStatusCode");

LoginExpiredError.js

class LoginExpiredError extends CustomError {
  constructor(message) {
    message = message || "Your session has expired!";

    super(message);
  }
}

throw new LoginExpiredError();

当我们创建了各种自定义 Error 之后,我们可以在不同的场景去使用它们了,比如在 http 请求失败的时候抛出 HttpRequestError,并弹出对话框提示用户。在用户登录过期之后,抛出 LoginExpiredError,弹出对话框,并自动 logout 等等。通过抛出不同的 Error 类型,才能让我们进行不同的扑获处理。

  const deffer = Q.deffer();

  if (expired) {
    deffer.reject(new LoginExpiredError()); // 通过 promise 抛出异常
  }

  if (error instanceof LoginExpiredError) { // 扑获异常
    logout();
  }
结尾

关于 Error 的介绍就先讲到这里。

  • Error in MDN ↩

  • window.onerror ↩

  • 文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/88005.html

    相关文章

    • JS笔记

      摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

      rottengeek 评论0 收藏0
    • 精读《你不知道的javascript(中卷)》

      摘要:强制类型转换本章介绍了的数据类型之间的转换即强制类型转换包括显式和隐式。强制类型转换常常为人诟病但实际上很多时候它们是非常有用的。隐式强制类型转换则没有那么明显是其他操作的副作用。在处理强制类型转换的时候要十分小心尤其是隐式强制类型转换。 前言 《你不知道的 javascript》是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaSc...

      李世赞 评论0 收藏0
    • Vue.js@2.6.10更新内置错误机制,Fundebug同步支持相应错误监控

      摘要:摘要的错误监控插件同步支持异步错误监控。此次更新,我们对的监控插件做了相应的更新,来更好地支持使用框架开发的应用错误的监控。程序运行后,成功捕获该错误总结更新到,对错误处理提供了更加强大的支持。 摘要: Fundebug 的 JavaScript 错误监控插件同步支持 Vue.js 异步错误监控。 Vue.js 从诞生至今已经 5 年,尤大在今年 2 月份发布了重大更新,即Vue 2....

      DC_er 评论0 收藏0
    • JavaScript工作机制:第1部分

      摘要:工作机制第部分本文转载自众成翻译译者网络埋伏纪事链接原文随着越来越受欢迎,开发团队正在将其用在技术栈的各个方面,包括前端后端混合应用嵌入式设备等等。之后,步骤将是如下这样调用栈中的每个条目称为栈帧。 JavaScript工作机制:第1部分 本文转载自:众成翻译译者:网络埋伏纪事链接:http://www.zcfy.cc/article/3965原文:https://blog.sessi...

      xiaodao 评论0 收藏0
    • JavaScripr中常遇到的错误和this关键字

      摘要:通过使用提供的异常处理语句,可以用结构化的方式来捕捉发生的错误,让异常处理带啊与核心业务代码实现分离。错误与异常处理在应用中的重要性是毋庸置疑的。包括内置对象函数在内的所有函数都可以用来调用,这种函数调用被称为构造函数调用。 错误与异常 概念 错误与异常是什么错误,指程序中的费正常运行状态,在其他编程语言中称为‘异常’或‘错误’。解释器会为每一个错误创建并抛出一个Error对象,其中包...

      klinson 评论0 收藏0

    发表评论

    0条评论

    AbnerMing

    |高级讲师

    TA的文章

    阅读更多
    最新活动
    阅读需要支付1元查看
    <