ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のエラー例外の分析 (例付き)

JavaScript のエラー例外の分析 (例付き)

不言
不言転載
2019-04-04 11:22:422499ブラウズ

この記事の内容は JavaScript のエラーと例外の分析に関するものです (例付き)。一定の参考価値があります。困っている友人が参考になれば幸いです。手伝う。

私のアドバイスは、エラーを隠すのではなく、勇気を持ってエラーを破棄することです。コードのバグによってプログラムがクラッシュしても、誰も恥じることはありません。プログラムを中断して、ユーザーが最初からやり直せるようにすることができます。間違いは避けられず、それにどう対処するかが重要です。

JavaScript は、一連のエラー処理メカニズムを提供します。エラーとは、プログラムの通常のフローを妨げる異常な事故です。では、オンラインにした後に特別なバグに遭遇した場合、どうすれば問題をより早く特定できるでしょうか?これが私たちのトピックで議論する必要があることです。

以下では、JavaScript Error の基本的な知識、例外をインターセプトしてキャプチャする方法、オンラインでエラーを簡単に報告する方法について説明します。また、インターネット上の知識ポイントに基づいていくつかの要約と分析を作成しました (私は単なるインターネットのポーターです)(作成者ではなく作業者です))、間違いや脱落がある場合は、その問題に関して私を厳しく批判してください。

このトピックは現在ブラウザーを対象としており、node.js は考慮されていませんが、これらはすべて JavaScript Es6 構文であり、類似しています。

JavaScript がエラーをスローするのはどのような場合ですか?

一般に 2 つの状況に分けられます。

JavaScript にエラーが発生する

開発者によって積極的にスローされるエラー

JavaScript エンジンが自動的にスローするエラー

ほとんどのシナリオで発生するエラーは、このタイプのエラーです。 Javascript の構文エラー、コード参照エラー、型エラーなどが発生した場合、JavaScript エンジンは自動的にそのようなエラーをトリガーします。いくつかのシナリオは次のとおりです。

シナリオ 1

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

シナリオ 2

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

構文エラー。ブラウザは通常、できるだけ早くエラーをスローし、実行まで待機しません。エラー。

シナリオ 3

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

手動でスローされたエラー

は、通常、クラス ライブラリによって開発されたカスタム エラー例外です (パラメーターがスローされるなどの不正なエラー例外など)。または、理解を容易にするためにエラー メッセージを再修正して報告してください。

シーン 1

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.

シナリオ 2

もちろん、必ずしもこれを行う必要はありません。

let data;

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

Error オブジェクトを作成するにはどうすればよいですか?

作成構文は次のとおりです。

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

新しい構文を省略した場合も同様です。

fileName と lineNumber はすべてのブラウザと互換性があるわけではなく、Google もサポートしていないため、無視してかまいません。

Error コンストラクターは一般的なエラー型であり、Error 型の他に、TypeError、RangeError などの型もあります。

エラー例

ここにリストされているのは、エラー層のプロトタイプ チェーンのプロパティとメソッドです。より深いプロトタイプ チェーンの継承プロパティと利便性については説明しません。一部の互換性のある、一般的ではないプロパティおよびメソッドについては説明されていません。

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

他のエラー型コンストラクターは Error を継承し、インスタンスは一貫しています。

プロパティ

Error.prototype.message

エラー メッセージ、Error("msg").message === "msg"。

Error.prototype.name

エラー タイプ (名前)、Error("msg").name === "エラー"。 TypeError の場合、名前は TypeError です。

Error.prototype.stack

Error オブジェクトは、非標準のスタック属性として関数トレース メソッドを提供します。関数がどのモードから、どの行またはファイルから、どのようなパラメータで呼び出されるかは関係ありません。このスタックは最も古い最近の呼び出しから生成され、元のグローバル スコープ呼び出しを返します。

これは仕様ではなく、互換性があります。テストの結果、Google、Firefox、Edge、Safari はすべてこの機能をサポートしています (すべて最新バージョン 2019-04-02 でテスト済み) が、IE はこの機能をサポートしていません。

Method

Error.prototype.constructor

Error.prototype.toString

戻り値の形式は ${name} です: ${メッセージ}。

一般的に使用されるエラー タイプ

一般的なエラー コンストラクターに加えて、JavaScript には他の 5 つの一般的なエラー コンストラクターもあります。

TypeError

エラー インスタンスを作成して、エラーの原因を示します: 変数またはパラメーターが有効な型に属していません。

throw TypeError("类型错误");
// Uncaught TypeError: 类型错误

RangeError

エラー インスタンスを作成して、エラーの原因を示します。数値変数またはパラメータが有効範囲を超えています。

throw RangeError("数值超出有效范围");
// Uncaught RangeError: 数值超出有效范围

ReferenceError

エラーの原因を示すエラー インスタンスを作成します: 無効な参照。

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 是无法捕获到网络异常的错误、