JavaScript エラー処理の究極のガイド — SitePoint

このチュートリアルでは JavaScript のエラー処理について深く掘り下げ、独自のエラーをスロー、検出、および処理できるようにします。

コンテンツ:

  1. エラー メッセージを表示するのは最後の手段です
  2. JavaScript がエラーを処理する方法
  3. 例外のキャッチ
  4. 標準の JavaScript エラーの種類
  5. AggregateError
  6. 独自の例外をスローする
  7. 非同期関数エラー
  8. Promise ベースのエラー
  9. 例外的な例外処理

エキスパートの開発者は予期せぬことを期待しています。 何かがうまくいかない場合、それはうまくいかない – 通常、最初のユーザーが新しい Web システムにアクセスした瞬間.

次のような Web アプリケーション エラーを回避できます。

  • 優れたエディターまたはリンターは、構文エラーをキャッチできます。
  • 適切な検証により、ユーザー入力エラーをキャッチできます。
  • 堅牢なテスト プロセスは、論理エラーを見つけることができます。

それでもエラーは残ります。 ブラウザーが失敗するか、使用している API をサポートしていない可能性があります。 サーバーが失敗したり、応答に時間がかかりすぎたりする可能性があります。 ネットワーク接続が失敗したり、信頼できなくなったりする可能性があります。 問題は一時的なものかもしれませんが、そのような問題を回避する方法をコーディングすることはできません。 ただし、問題を予測し、是正措置を講じて、アプリケーションの回復力を高めることができます。

エラー メッセージを表示するのは最後の手段です

理想的には、ユーザーにエラー メッセージが表示されないようにする必要があります。

装飾画像の読み込みに失敗するなどの軽微な問題は無視できる場合があります。 データをローカルに保存し、後でアップロードすることで、Ajax データ保存の失敗などのより深刻な問題に対処できます。 ユーザーがデータを失う危険性がある場合にのみ、エラーが必要になります — 彼らがそれについて何かできると仮定して.

したがって、発生したエラーをキャッチして、最善のアクションを決定する必要があります。 JavaScript アプリケーションでエラーを発生させてキャッチすることは、最初は気が遠くなるかもしれませんが、思ったより簡単かもしれません。

JavaScript がエラーを処理する方法

JavaScript ステートメントがエラーになると、 例外をスローする. JavaScript は、 Error エラーを説明するオブジェクト。 これは、この CodePen デモで実際に確認できます。 設定すると 小数位 負の数にすると、コンソールの下部にエラー メッセージが表示されます。 (このチュートリアルでは CodePens を埋め込んでいないことに注意してください。これは、意味を理解するためにコンソール出力を表示できる必要があるためです。)

結果は更新されません。 RangeError コンソールのメッセージ。 次の関数は、次の場合にエラーをスローします。 dp は負です:


function divide(v1, v2, dp) {

  return (v1 / v2).toFixed(dp);

}

エラーをスローした後、JavaScript インタープリターは例外処理コードをチェックします。 には何も存在しません divide() 関数なので、呼び出し元の関数をチェックします。


function showResult() {

  result.value = divide(
    parseFloat(num1.value),
    parseFloat(num2.value),
    parseFloat(dp.value)
  );

}

インタープリターは、次のいずれかが発生するまで、コール スタックのすべての関数に対してプロセスを繰り返します。

  • 例外ハンドラを見つけます
  • コードのトップ レベルに到達します (上記の CodePen の例で示されているように、プログラムが終了し、コンソールにエラーが表示されます)。

例外のキャッチ

例外ハンドラを divide() try…catch ブロックを使用した関数:


function divide(v1, v2, dp) {
  try {
    return (v1 / v2).toFixed(dp);
  }
  catch(e) {
    console.log(`
      error name   : ${ e.name }
      error message: ${ e.message }
    `);
    return 'ERROR';
  }
}

これにより、 try {} ブロックしますが、例外が発生すると、 catch {} ブロックが実行され、スローされたエラー オブジェクトを受け取ります。 前回同様、設定してみる 小数位 この CodePen デモでは負の数にします。

お茶 結果 今ショー エラー. コンソールにはエラー名とメッセージが表示されますが、これは console.log プログラムを終了しません。

注: このデモンストレーションの try...catch ブロックは、次のような基本的な機能には過剰です divide(). 確認するのは簡単です dp 以下に示すように、ゼロ以上です。

オプションを定義できます finally {} 次のいずれかの場合にコードを実行する必要がある場合はブロックします trycatch 実行されたコード:

function divide(v1, v2, dp) {
  try {
    return (v1 / v2).toFixed(dp);
  }
  catch(e) {
    return 'ERROR';
  }
  finally {
    console.log('done');
  }
}

コンソール出力 "done"、計算が成功するか、エラーが発生するか。 もっている finally ブロックは通常、そうでなければ両方で繰り返す必要があるアクションを実行します try そしてその catch ブロック — API 呼び出しのキャンセルやデータベース接続の終了など。

もっている try ブロックには次のいずれかが必要です catch ブロック、 finally ブロック、またはその両方。 注意してください。 finally ブロックには return ステートメント、その値は関数全体の戻り値になります。 他の return のステートメント trycatch ブロックは無視されます。

ネストされた例外ハンドラ

呼び出し元に例外ハンドラを追加するとどうなるか showResult() 関数?


function showResult() {

  try {
    result.value = divide(
      parseFloat(num1.value),
      parseFloat(num2.value),
      parseFloat(dp.value)
    );
  }
  catch(e) {
    result.value = 'FAIL!';
  }

}

答えは… なし! この catch ブロックに到達することはありません。 catch のブロック divide() 関数はエラーを処理します。

ただし、プログラムで新しい Error オブジェクト divide() オプションで元のエラーを cause 2 番目の引数のプロパティ:

function divide(v1, v2, dp) {
  try {
    return (v1 / v2).toFixed(dp);
  }
  catch(e) {
    throw new Error('ERROR', { cause: e });
  }
}

これにより、 catch 呼び出し関数のブロック:


function showResult() {

  try {
    
  }
  catch(e) {
    console.log( e.message ); 
    console.log( e.cause.name ); 
    result.value = 'FAIL!';
  }

}

標準の JavaScript エラーの種類

例外が発生すると、JavaScript は次のいずれかのタイプを使用して、エラーを説明するオブジェクトを作成してスローします。

構文エラー

括弧の欠落など、構文的に無効なコードによってスローされるエラー:

if condition) { 
  console.log('condition is true');
}

注: C++ や Java などの言語は、コンパイル中に構文エラーを報告します。 JavaScript は解釈された言語であるため、コードが実行されるまで構文エラーは識別されません。 優れたコード エディターまたはリンターは、コードを実行する前に構文エラーを見つけることができます。

参照エラー

存在しない変数にアクセスするとスローされるエラー:

function inc() {
  value++; 
}

繰り返しになりますが、優れたコード エディターとリンターはこれらの問題を発見できます。

TypeError

存在しないオブジェクト メソッドの呼び出しなど、値が期待される型ではない場合にスローされるエラー:

const obj = {};
obj.missingMethod(); 

範囲エラー

値が許可された値のセットまたは範囲内にない場合にスローされるエラー。 上記で使用された toFixed() メソッドは、通常 0 から 100 の間の値を予期するため、このエラーを生成します。

const n = 123.456;
console.log( n.toFixed(-1) ); 

URI エラー

不正な形式の URI に遭遇したときに、encodeURI() や decodeURI() などの URI 処理関数によってスローされるエラー:

const u = decodeURIComponent('%'); 

EvalError

無効な JavaScript コードを含む文字列を eval() 関数に渡すときにスローされるエラー:

eval('console.logg x;'); 

注:使用しないでください eval()! ユーザー入力から構築された可能性のある文字列に含まれる任意のコードを実行することは、非常に危険です!

AggregateError

複数のエラーが 1 つのエラーにラップされている場合にスローされるエラー。 これは通常、任意の数の promise から結果を返す Promise.all() などの操作を呼び出すときに発生します。

内部エラー

JavaScript エンジン内部でエラーが発生した場合にスローされる非標準 (Firefox のみ) のエラー。 これは通常、大きな配列や「再帰が多すぎる」など、何かがメモリを使いすぎた結果です。

エラー

最後に、ジェネリックがあります Error オブジェクトは、独自の例外を実装するときに最も頻繁に使用されます。これについては次に説明します。

独自の例外をスローする

私たちはできる throw エラーが発生した場合の独自の例外 — または発生する必要があります. 例えば:

  • 関数に有効なパラメータが渡されていません
  • Ajax リクエストが期待されるデータを返さない
  • ノードが存在しないため、DOM の更新が失敗する

お茶 throw ステートメントは、実際には任意の値またはオブジェクトを受け入れます。 例えば:

throw 'A simple error string';
throw 42;
throw true;
throw { message: 'An error', name: 'MyError' };

例外によってインターセプトされるまで、コール スタック上のすべての関数に例外がスローされます (catch) ハンドラー。 ただし、より実際には、作成してスローしたいと思うでしょう Error JavaScript によってスローされる標準エラーと同じように動作します。

ジェネリックを作成できます Error オプションのメッセージをコンストラクターに渡すことによってオブジェクトを作成します。

throw new Error('An error has occurred');

使用することもできます Error なしの関数のように new. を返します。 Error 上記と同一のオブジェクト:

throw Error('An error has occurred');

必要に応じて、ファイル名と行番号を 2 番目と 3 番目のパラメーターとして渡すことができます。

throw new Error('An error has occurred', 'script.js', 99);

これが必要になることはめったにありません。 Error 物体。 (ファイルが変更されるため、それらを維持することも困難です!)

ジェネリックを定義できます Error オブジェクトですが、可能であれば標準のエラー型を使用する必要があります。 例えば:

throw new RangeError('Decimal places must be 0 or greater');

全て Error オブジェクトには次のプロパティがあり、 catch ブロック:

  • .name: エラータイプの名前 — など ErrorRangeError
  • .message: エラーメッセージ

次の非標準プロパティも Firefox でサポートされています。

  • .fileName: エラーが発生したファイル
  • .lineNumber: エラーが発生した行番号
  • .columnNumber: エラーが発生した行の列番号
  • .stack: エラーが発生する前に行われた関数呼び出しをリストするスタック トレース

私たちは変更することができます divide() を投げる関数 RangeError 小数点以下の桁数が数値ではない、0 より小さい、または 8 より大きい場合:


function divide(v1, v2, dp) {

  if (isNaN(dp) || dp < 0 || dp > 8) {
    throw new RangeError('Decimal places must be between 0 and 8');
  }

  return (v1 / v2).toFixed(dp);
}

同様に、 ErrorTypeError いつ 配当 値は防止する数値ではありません NaN 結果:

  if (isNaN(v1)) {
    throw new TypeError('Dividend must be a number');
  }

対応も可能です 仕切り 非数値またはゼロです。 JavaScript はゼロで除算すると Infinity を返しますが、これはユーザーを混乱させる可能性があります。 ジェネリックを上げるのではなく Error、カスタムを作成できます DivByZeroError エラーの種類:


class DivByZeroError extends Error {
  constructor(message) {
    super(message);
    this.name = 'DivByZeroError';
  }
}

次に、同じ方法で投げます。

if (isNaN(v2) || !v2) {
  throw new DivByZeroError('Divisor must be a non-zero number');
}

今すぐ追加 try...catch 呼び出しをブロックする showResult() 関数。 いくらでも受け取れる Error 入力してそれに応じて反応します — この場合、エラーメッセージを表示します:


function showResult() {

  try {
    result.value = divide(
      parseFloat(num1.value),
      parseFloat(num2.value),
      parseFloat(dp.value)
    );
    errmsg.textContent = '';
  }
  catch (e) {
    result.value = 'ERROR';
    errmsg.textContent = e.message;
    console.log( e.name );
  }

}

この CodePen デモに無効な非数値、ゼロ、および負の値を入力してみてください。

の最終バージョン divide() 関数はすべての入力値をチェックし、適切な Error 必要に応じて:


function divide(v1, v2, dp) {

  if (isNaN(v1)) {
    throw new TypeError('Dividend must be a number');
  }

  if (isNaN(v2) || !v2) {
    throw new DivByZeroError('Divisor must be a non-zero number');
  }

  if (isNaN(dp) || dp < 0 || dp > 8) {
    throw new RangeError('Decimal places must be between 0 and 8');
  }

  return (v1 / v2).toFixed(dp);
}

を配置する必要がなくなりました。 try...catch ファイナルの周りをブロック return、エラーを生成するべきではないためです。 エラーが発生した場合、JavaScript は独自のエラーを生成し、 catch ブロックイン showResult().

非同期関数エラー

コールバックベースの非同期関数によってスローされた例外をキャッチすることはできません。 try...catch ブロックは実行を完了します。 このコードは正しいように見えますが、 catch ブロックは決して実行されず、コンソールに Uncaught Error 1 秒後のメッセージ:

function asyncError(delay = 1000) {

  setTimeout(() => {
    throw new Error('I am never caught!');
  }, delay);

}

try {
  asyncError();
}
catch(e) {
  console.error('This will never run');
}

ほとんどのフレームワークや Node.js などのサーバー ランタイムで想定されている規則は、コールバック関数の最初のパラメーターとしてエラーを返すことです。 例外は発生しませんが、手動でスローすることはできます Error 必要であれば:

function asyncError(delay = 1000, callback) {

  setTimeout(() => {
    callback('This is an error message');
  }, delay);

}

asyncError(1000, e => {

  if (e) {
    throw new Error(`error: ${ e }`);
  }

});

Promise ベースのエラー

コールバックは扱いにくくなる可能性があるため、非同期コードを記述するときは promise を使用することをお勧めします。 エラーが発生すると、promise の reject() メソッドは新しいを返すことができます Error オブジェクトまたはその他の値:

function wait(delay = 1000) {

  return new Promise((resolve, reject) => {

    if (isNaN(delay) || delay < 0) {
      reject( new TypeError('Invalid delay') );
    }
    else {
      setTimeout(() => {
        resolve(`waited ${ delay } ms`);
      }, delay);
    }

  })

}

注: 関数は 100% 同期または 100% 非同期である必要があります。 このため、次の項目を確認する必要があります。 delay 返された promise 内の値。 私たちがチェックした場合 delay 値とエラーをスローしました promise を返すと、エラーが発生したときに関数が同期になります。

Promise.catch() メソッドは、無効な delay パラメータを受け取り、返された Error 物体:


wait('INVALID')
  .then( res => console.log( res ))
  .catch( e => console.error( e.message ) )
  .finally( () => console.log('complete') );

個人的には、Promise チェーンは少し読みにくいと思います。 幸いなことに、使用できます await プロミスを返す関数を呼び出す。 これは、 async 関数ですが、標準を使用してエラーをキャプチャできます try...catch ブロック。

以下(即時起動) async function は、機能的に上記の promise チェーンと同じです。

(async () => {

  try {
    console.log( await wait('INVALID') );
  }
  catch (e) {
    console.error( e.message );
  }
  finally {
    console.log('complete');
  }

})();

例外的な例外処理

投げ Error オブジェクトと例外の処理は、JavaScript では簡単です。

try {
  throw new Error('I am an error!');
}
catch (e) {
  console.log(`error ${ e.message }`)
}

エラーに適切に反応し、ユーザーの生活を楽にする回復力のあるアプリケーションを構築することは、さらに困難です。 常に予想外のことを期待してください。

さらに詳しい情報:

Leave a Comment

Your email address will not be published. Required fields are marked *