JavaScript之毛病非常讨论

同步宣布于 https://github.com/xianshanna…

我的发起是不要隐蔽毛病,勇敢地抛出来。没有人会由于代码涌现 bug 致使顺序崩溃而羞辱,我们能够让顺序中缀,让用户重来。毛病是没法防止的,怎样去处置惩罚它才是最主要的。

JavaScript 供应一套毛病处置惩罚机制,毛病是滋扰顺序寻常流程的非寻常的变乱。而没人能够坚持顺序没有 bug,那末上线后碰到特别的 bug,怎样更快的定位题目所在呢?这就是我们这个专题须要议论的题目。

下面会从 JavaScript Error 基础知识、怎样阻拦和捕捉非常、怎样轻易的在线上报毛病等方面来叙说,本人也是依据网上的知识点举行了一些总结和剖析(我只是互联网的搬运工,不是创造者),假如有什么讹夺的状况,请在 issue 上狠狠的指摘我。

这个专题如今是针对浏览器的,还没斟酌到 node.js,不过都是 JavaScript Es6 语法,迥然不同。

什么时刻 JavaScript 会抛出毛病呢?

寻常分为两种状况:

  • JavaScript 自带毛病
  • 开辟者主动抛出的毛病

JavaScript 引擎自动抛出的毛病

大多数场景下我们碰到的毛病都是这类毛病。假如发作Javscript 语法毛病、代码援用毛病、范例毛病等,JavaScript 引擎就会自动触发此类毛病。以下一些场景:

  • 场景一

    console.log(a.notExited)
    // 浏览器会抛出 Uncaught ReferenceError: a is not defined
  • 场景二

    const a;
    // 浏览器抛出 Uncaught SyntaxError: Missing initializer in const declaration

    语法毛病,浏览器寻常第一时间就抛出毛病,不会比及实行的时刻才会报错。

  • 场景三

    let data;
    data.forEach(v=>{})
    // Uncaught TypeError: Cannot read property 'forEach' of undefined

手动抛出的毛病

寻常都是类库开辟的自定义毛病非常(如参数等不合法的毛病非常抛出)。或许从新修正毛病 message 举行上报,以轻易明白。

  • 场景一

    function sum(a,b){
      if(typeof a !== 'number') {
        throw TypeError('Expected a to be a number.');
      }
      if(typeof b !== 'number') {
        throw TypeError('Expected b to be a number.');
      }
      return a + b;
    }
    sum(3,'d');
    // 浏览器抛出 uncaught TypeError: Expected b to be a number.
  • 场景二

    固然我们不一定须要如许做。

    let data;
    
    try {
      data.forEach(v => {});
    } catch (error) {
      error.message = 'data 没有定义,data 必需是数组。';
      error.name = 'DataTypeError';
      throw error;
    }

怎样建立 Error 对象?

建立语法以下:

new Error([message[,fileName,lineNumber]])

省略 new 语法也一样。

个中fileNamelineNumber 不是一切浏览器都兼容的,谷歌也不支撑,所以能够疏忽。

Error 组织函数是通用毛病范例,除了 Error 范例,另有 TypeErrorRangeError 等范例。

Error 实例

这里枚举的都是 Error 层的原型链属性和要领,更深层的原型链的继续属性和轻易不做申明。一些有兼容性的而且不经常运用的属性和要领不做申明。

console.log(Error.prototype)
// 浏览器输出 {constructor: ƒ, name: "Error", message: "", toString: ƒ}

其他毛病范例组织函数是继续 Error,实例是一致的。

属性

  • Error.prototype.message

    毛病信息, Error("msg").message === "msg"

  • Error.prototype.name

    毛病范例(名字), Error("msg").name === "Error”。假如是 TypeError,那末 name 为 TypeError。

  • Error.prototype.stack

    Error 对象作为一个非标准的栈属性供应了一种函数追踪体式格局。不管这个函数被被挪用,处于什么形式,来自于哪一行或许哪一个文件,有着什么样的参数。这个栈产生于近来一次挪用最早的那次挪用,返回原始的全局作用域挪用。

    这个不是范例,存在兼容性。经测试,谷歌、火狐、Edge、safar 都支撑此特征(都是在最新的版本下测试 2019-04-02),IE 不支撑。

要领

  • Error.prototype.constructor
  • Error.prototype.toString

    返回值花样为 ${name}: ${message}

经常运用 Error 范例

除了通用的 Error 组织函数外,JavaScript另有罕见的 5 个其他范例的毛病组织函数。

TypeError

建立一个 Error 实例,示意毛病的缘由:变量或参数不属于有效范例

throw TypeError("范例毛病");
// Uncaught TypeError: 范例毛病

RangeError

建立一个 Error 实例,示意毛病的缘由:数值变量或参数超越其有效局限

throw RangeError("数值超越有效局限");
// Uncaught RangeError: 数值超越有效局限

ReferenceError

建立一个 Error 实例,示意毛病的缘由:无效援用

throw ReferenceError("无效援用");
// Uncaught ReferenceError: 无效援用

SyntaxError

建立一个 Error 实例,示意毛病的缘由:语法毛病。这类场景很罕用,除非类库定义了新语法(如模板语法)。

throw SyntaxError("语法毛病");
// Uncaught SyntaxError: 语法毛病

URIError

建立一个 Error 实例,示意毛病的缘由:涉及到 uri 相干的毛病

throw URIError("url 不合法");
// Uncaught RangeError: url 不合法

自定义 Error 范例

自定义新的 Error 范例须要继续 Error ,以下自定义 CustomError

function CustomError(...args){
  class InnerCustomError extends Error {
    name = "CustomError";
  }
  return new InnerCustomError(...args);
}

继续 Error 后,我们只须要对 name 做重写,然后封装成可直接挪用的函数即可。

怎样阻拦 JavaScript 毛病?

既然没人能保证 web 运用不会涌现 bug,那末涌现非常报错时,怎样阻拦并举行一些操纵呢?

try…catch… 阻拦

这是阻拦 JavaScript 毛病,阻拦后,假如不手动抛出毛病,这个毛病将寂静处置惩罚。寻常写代码假如我们晓得某段代码可能会涌现报错题目,就能够运用这类体式格局。以下:

const { data } = this.props;
try {
  data.forEach(d=>{});
  // 假如 data 不是数组就会报错
} catch(err){
  console.error(err);
  // 这里能够做上报处置惩罚等操纵
}

一些运用体式格局

非常不友爱的处置惩罚体式格局

try...catch... 运用须要注重,try…catch… 后,毛病会被阻拦,假如不主动抛出毛病,那末没法晓得报错位置。以下面的处置惩罚体式格局就是不好的。

function badHandler(fn) {
  try {
    return fn();
  } catch (err) { /**noop,不做任何处置惩罚**/ }
  return null;
}
badHandler();

如许 fn 回调发送毛病后,我们没法晓得毛病是在那里发作的,由于已被 try…catch 了,那末怎样处理这个题目呢?

相对友爱但蹩脚的处置惩罚体式格局
function CustomError(...args){
  class InnerCustomError extends Error {
    name = "CustomError";
  }
  return new InnerCustomError(...args);
}
function uglyHandlerImproved(fn) {
  try {
    return fn();
  } catch (err) { 
    throw new CustomError(err.message);
  }
  return null;
}
badHandler();

如今,这个自定义的毛病对象包括了底本毛病的信息,因而变得越发有效。然则由于再度抛出来,依然是未处置惩罚的毛病。

try…catch… 能够阻拦异步毛病吗?

这个也要分场景,也看个人的明白方向,起首明白下面这句话:

try…catch 只会阻拦当前实行环境的毛病,try 块中的异步已离开了当前的实行环境,所以 try…catch… 无效。

setTimeoutPromise 都没法经由过程 try…catch 捕捉到毛病,指的是 try 包括异步(非当前实行环境),不是异步包括 try(当前实行环境)。异步无效和有效 try…catch 以下:

  • setTimeout

    这个无效:

    try {
      setTimeout(() => {
        data.forEach(d => {});
      });
    } catch (err) {
      console.log('这里不会运转');
    }

    下面的 try…catch 才会有效:

    setTimeout(() => {
      try {
        data.forEach(d => {});
      } catch (err) {
        console.log('这里会运转');
      }
    });
  • Promise

    这个无效:

    try {
      new Promise(resolve => {
        data.forEach(d => {});
        resolve();
      });
    } catch (err) {
      console.log('这里不会运转');
    }

    下面的 try…catch 才会有效:

    new Promise(resolve => {
      try {
        data.forEach(d => {});
      } catch (err) {
        console.log('这里会运转');
      }
    });

小结

不是一切场景都须要 try…catch… 的,假如一切须要的处所都 try…catch,那末代码将变得痴肥,可读性变差,开辟效力变低。那末我须要一致猎取毛病信息呢?有无更好的处置惩罚体式格局?固然有,后续会提到。

Promise 毛病阻拦

Promise.prototype.catch 能够到达 try…catch 一样的结果,只如果在 Promise 相干的处置惩罚中报错,都会被 catch 到。固然假如你在相干回调函数中 try…catch,然后做了寂静提醒,那末也是 catch 不到的。

以下会被 catch 到:

new Promise(resolve => {
  data.forEach(v => {});
}).catch(err=>{/*这里会运转*/})

下面的不会被 catch 到:

new Promise(resolve => {
  try {
      data.forEach(v => {});
  }catch(err){}
}).catch(err=>{/*这里不会运转*/})

Promise 毛病阻拦,这里就不细致说了,假如你看懂了 try…catch,这个也很好明白。

setTimeout 等其他异步毛病阻拦呢?

如今没有相干的体式格局直接阻拦 setTimeout 等其他异步操纵。

假如要阻拦 setTimeout 等异步毛病,我们须要在异步回调代码中处置惩罚,如:

setTimeout(() => {
  try {
    data.forEach(d => {});
  } catch (err) {
    console.log('这里会运转');
  }
});

如许能够阻拦到 setTimeout 回调发作的毛病,然则假如是下面如许 try…catch 是无效的:

try {
  setTimeout(() => {
    data.forEach(d => {});
  });
} catch (err) {
  console.log('这里不会运转');
}

怎样猎取 JavaScript 毛病信息?

你能够运用上面阻拦毛病信息的体式格局猎取到毛病信息。然则呢,你要每一个场景都要去阻拦一遍吗?起首我们不确定什么处所会发作毛病,然后我们也不可能每一个处所都去阻拦毛病。

不必忧郁,JavaScript 也斟酌到了这一点,供应了一些便利的猎取体式格局(不是阻拦,毛病照样会停止顺序的运转,除非主动阻拦了)。

window.onerror 事宜猎取毛病信息

onerror 事宜不管是异步照样非异步毛病(除了 Promise 毛病),onerror 都能捕捉到运转时毛病。

须要注重一下几点:

  • window.onerror 函数只要在返回 true 的时刻,非常才不会向上抛出,不然即使是晓得非常的发作控制台照样会显现 Uncaught Error: xxxxx。假如运用 addEventListenerevent.preventDefault() 能够到达一样的结果。
  • window.onerror 是没法捕捉到收集非常的毛病、<img/><script>资本加载失利等毛病。不过假如你运用了 fetch 等支撑 promise 的体式格局,毛病能够经由过程 unhandledrejection 体式格局拿到毛病信息。

语法:

  • window.onerror

    window.onerror = function(message, source, lineno, colno, error) { ... }
  • window.addEventListener(‘error’)

    window.addEventListener('error', function(event) { ... })

细致看这里

unhandledrejection 事宜猎取 Promise 毛病

unhandledrejection 存在兼容性题目,IE、Edge、火狐等如今都不支撑。

用法以下:

window.addEventListener("unhandledrejection", event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
});

或许

window.onunhandledrejection = event => {
  console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`);
};

细致看这里

怎样处置惩罚毛病信息?

毛病对象 Error 包括以下的字段返回:

  • name

    毛病的范例,寻常有 ErrorTypeErrorReferenceErrorRangeErrorURIError 等,固然也能够是自定义的毛病范例。

  • message

    毛病的细致信息,能够是 JavaScript 自动抛出的毛病信息,也能够是手动抛出的自定义信息。

  • stack

    这个不是范例,存在兼容性。经测试,谷歌、火狐、Edge、safar 都支撑此特征(都是在最新的版本下测试 2019-04-02),IE 不支撑。

namemessage 我们都不必做什么处置惩罚,主如果要针对 stack 做处置惩罚,寻常我们须要把,这三个字段的信息提交到毛病处置惩罚系统,针对性处置惩罚。

同时我们天生环境的代码是被紧缩后的代码,须要运用 sourceMap 举行映照复原代码。

后续会补充这个议论

参考文章

    原文作者:xianshannan
    原文地址: https://segmentfault.com/a/1190000018757095
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞