>  기사  >  웹 프론트엔드  >  JavaScript의 오류 예외 분석(예제 포함)

JavaScript의 오류 예외 분석(예제 포함)

不言
不言앞으로
2019-04-04 11:22:422487검색

이 기사의 내용은 JavaScript의 오류 예외 분석에 관한 것입니다(예제 포함). 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

제 조언은 실수를 숨기지 말고 용기를 내어 버릴 수 있다는 것입니다. 코드의 버그로 인해 프로그램이 중단되면 누구도 부끄러워해서는 안 됩니다. 프로그램을 중단하고 사용자가 다시 시작하도록 할 수 있습니다. 실수는 피할 수 없으며, 이를 어떻게 처리하는가가 중요합니다.

JavaScript는 오류 처리 메커니즘을 제공합니다. 오류는 프로그램의 정상적인 흐름을 방해하는 비정상적인 사고입니다. 그리고 누구도 프로그램을 버그 없는 상태로 유지할 수 없습니다. 따라서 온라인에 접속한 후 특별한 버그를 발견하면 어떻게 문제를 더 빨리 찾을 수 있습니까? 이것이 우리가 논의해야 할 주제입니다.

다음은 JavaScript 오류에 대한 기본 지식, 예외를 가로채고 캡처하는 방법, 온라인으로 오류를 편리하게 보고하는 방법에 대해 설명합니다. 또한 인터넷에서 지식 포인트를 기반으로 몇 가지 요약 및 분석을 했습니다. 창작자가 아닌 인터넷) ), 오류나 누락이 있는 경우 해당 문제에 대해 엄중히 비판해 주시기 바랍니다.

이 주제는 현재 브라우저를 대상으로 하며 node.js는 고려되지 않았습니다. 그러나 모두 유사한 JavaScript Es6 구문입니다.

JavaScript는 언제 오류를 발생시키나요?

일반적으로 다음 두 가지 상황으로 나뉩니다.

JavaScript 내장 오류

개발자가 적극적으로 발생하는 오류

JavaScript 엔진에서 자동으로 발생하는 오류

이것은 대부분의 클래스 오류 시나리오에서 발생하는 오류입니다. Javscript 구문 오류, 코드 참조 오류, 유형 오류 등이 발생하면 JavaScript 엔진이 자동으로 해당 오류를 트리거합니다. 일부 시나리오는 다음과 같습니다.

Scenario 1

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

Scenario 2

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

Syntax error. 브라우저는 일반적으로 가능한 한 빨리 오류를 발생시키고 실행될 때까지 기다리지 않습니다.

시나리오 3

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

수동으로 발생한 오류

는 일반적으로 클래스 라이브러리에서 개발한 사용자 정의 오류 예외입니다(예: 매개변수에서 발생한 불법 오류 예외). 또는 이해를 돕기 위해 오류 메시지를 다시 수정하여 보고해 주세요.

Scene One

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.

Scene Two

물론 꼭 이렇게 할 필요는 없습니다.

let data;

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

오류 개체를 만드는 방법은 무엇입니까?

생성 구문은 다음과 같습니다.

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

새 구문을 생략하는 경우에도 마찬가지입니다.

fileName과 lineNumber는 모든 브라우저와 호환되지 않으며 Google에서도 지원하지 않으므로 무시해도 됩니다.

Error 생성자는 일반적인 오류 유형이며 TypeError, RangeError 및 기타 유형도 있습니다.

오류 인스턴스

여기에 나열된 모든 것은 오류 레이어의 프로토타입 체인 속성과 메서드입니다. 더 깊은 프로토타입 체인의 상속 속성과 편의성은 설명되지 않습니다. 일부 호환 가능하고 일반적이지 않은 속성과 방법은 설명되지 않습니다.

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

다른 오류 유형 생성자는 Error를 상속하며 인스턴스는 일관됩니다.

Property

Error.prototype.message

오류 메시지, Error("msg").message === "msg".

Error.prototype.name

오류 유형(이름), Error("msg").name === "Error". TypeError인 경우 이름은 TypeError입니다.

Error.prototype.stack

Error 객체는 비표준 스택 속성으로 함수 추적 방법을 제공합니다. 이 함수가 어떤 모드에서 호출되는지, 어떤 라인이나 파일에서, 어떤 매개변수를 사용하여 호출되는지에 상관없이 말이죠. 이 스택은 최초의 최근 호출에서 생성되어 원래 전역 범위 호출을 반환합니다.

사양은 아니고 호환성이 있습니다. 테스트 결과 Google, Firefox, Edge 및 Safari는 모두 이 기능을 지원하지만(모두 최신 버전 2019-04-02에서 테스트됨) IE는 이를 지원하지 않습니다.

Method

Error.prototype.constructor

Error.prototype.toString

반환 값 형식은 ${name}: ${message}입니다.

일반적으로 사용되는 오류 유형

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… 无效。

setTimeout 和 Promise 都无法通过 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。如果使用 addEventListener,event.preventDefault() 可以达到同样的效果。

window.onerror 是无法捕获到网络异常的错误、