プログラミングがどれほど優れていても、スクリプトにエラーが発生することがあります。これらは、私たちのミス、予期しないユーザー入力、サーバーの誤った応答など、さまざまな理由で発生する可能性があります。
通常、スクリプトはエラーが発生すると「停止」し、コンソールに出力されます。
ただし、エラーを「キャッチ」できる構文構造try...catch
があるため、スクリプトは終了する代わりに、より合理的な処理を行うことができます。
try...catch
コンストラクトには、 try
とcatch
という 2 つのメイン ブロックがあります。
試す { // コード... } キャッチ (エラー) { // エラー処理 }
次のように動作します。
まず、 try {...}
内のコードが実行されます。
エラーがなかった場合、 catch (err)
は無視されます。実行はtry
の終わりに達し、 catch
スキップして続行されます。
エラーが発生した場合、 try
実行は停止され、制御はcatch (err)
の先頭に流れます。 err
変数 (任意の名前を使用できます) には、何が起こったかの詳細を含むエラー オブジェクトが含まれます。
したがって、 try {...}
ブロック内のエラーによってスクリプトが強制終了されることはありません。これはcatch
で処理することができます。
いくつかの例を見てみましょう。
エラーのない例: alert
(1)
と(2)
を示します。
試す { alert('試行の開始'); // (1) <-- // ...ここにはエラーはありません alert('試行実行の終了'); // (2) <-- } キャッチ (エラー) { alert('エラーがないため、キャッチは無視されます'); // (3) }
エラーのある例: (1)
と(3)
を示します。
試す { alert('試行の開始'); // (1) <-- ラララ。 // エラー、変数が定義されていません! alert('試行の終了 (到達しません)'); // (2) } キャッチ (エラー) { alert(`エラーが発生しました!`); // (3) <-- }
try...catch
実行時エラーに対してのみ機能します
try...catch
機能するには、コードが実行可能である必要があります。つまり、有効な JavaScript である必要があります。
コードが構文的に間違っている場合、たとえば中括弧が一致していない場合は機能しません。
試す { {{{{{{{{{{{{ } キャッチ (エラー) { alert("エンジンはこのコードを理解できません。無効です"); }
JavaScript エンジンはまずコードを読み取り、次にそれを実行します。読み取りフェーズで発生するエラーは「解析時」エラーと呼ばれ、(コード内からは) 回復できません。それはエンジンがコードを理解できないためです。
したがって、 try...catch
有効なコードで発生したエラーのみを処理できます。このようなエラーは「実行時エラー」、または「例外」と呼ばれることもあります。
try...catch
同期的に動作します
setTimeout
などの「スケジュールされた」コードで例外が発生した場合、 try...catch
例外をキャッチしません。
試す { setTimeout(関数() { noSuchVariable; // スクリプトはここで終了します }, 1000); } キャッチ (エラー) { alert( "動作しません" ); }
これは、関数自体が後で、エンジンがtry...catch
構造をすでに終了したときに実行されるためです。
スケジュールされた関数内で例外をキャッチするには、 try...catch
その関数内にある必要があります。
setTimeout(関数() { 試す { noSuchVariable; // try...catch がエラーを処理します。 } キャッチ { alert( "ここでエラーが見つかりました!" ); } }, 1000);
エラーが発生すると、JavaScript はその詳細を含むオブジェクトを生成します。次に、オブジェクトは引数としてcatch
に渡されます。
試す { // ... } catch (err) { // <-- 「エラー オブジェクト」。err の代わりに別の単語を使用することもできます // ... }
すべての組み込みエラーについて、エラー オブジェクトには 2 つの主要なプロパティがあります。
name
エラー名。たとえば、未定義の変数の場合、それは"ReferenceError"
です。
message
エラーの詳細に関するテキストメッセージ。
ほとんどの環境では、その他の非標準プロパティを使用できます。最も広く使用され、サポートされているものの 1 つは次のとおりです。
stack
現在の呼び出しスタック: エラーの原因となったネストされた呼び出しのシーケンスに関する情報を含む文字列。デバッグ目的で使用されます。
例えば:
試す { ラララ。 // エラー、変数が定義されていません! } キャッチ (エラー) { アラート(err.name); // 参照エラー アラート(err.メッセージ); // ラララは定義されていません アラート(err.スタック); // ReferenceError: lalala が (...コール スタック) で定義されていません // エラー全体を表示することもできます // エラーは「name: message」として文字列に変換されます アラート(エラー); // ReferenceError: lalala が定義されていません }
最近の追加
これは言語に最近追加されたものです。 古いブラウザにはポリフィルが必要な場合があります。
エラーの詳細が必要ない場合、 catch
それを省略できます。
試す { // ... } catch { // <-- なし (エラー) // ... }
try...catch
の実際の使用例を見てみましょう。
すでにご存知のとおり、JavaScript は JSON エンコードされた値を読み取るための JSON.parse(str) メソッドをサポートしています。
通常、ネットワーク経由でサーバーまたは別のソースから受信したデータをデコードするために使用されます。
それを受け取り、次のようにJSON.parse
呼び出します。
let json = '{"名前":"ジョン", "年齢": 30}'; // サーバーからのデータ let user = JSON.parse(json); // テキスト表現を JS オブジェクトに変換します // これで、ユーザーは文字列のプロパティを持つオブジェクトになります アラート( ユーザー名 ); // ジョン アラート( user.age ); // 30
JSON の詳細については、JSON の章の JSON メソッドを参照してください。
json
形式が不正な場合、 JSON.parse
エラーを生成するため、スクリプトは「停止」します。
それで満足すべきでしょうか?もちろん違います!
こうすることで、データに何か問題がある場合でも、訪問者は (開発者コンソールを開かない限り) それを知ることはありません。そして、エラー メッセージも表示されずに何かが「ただ終了」することを人々は本当に好みません。
try...catch
使用してエラーを処理しましょう。
let json = "{ 悪い json }"; 試す { let user = JSON.parse(json); // <-- エラーが発生した場合... アラート( ユーザー名 ); // 機能しません } キャッチ (エラー) { // ...実行はここにジャンプします alert( "申し訳ありませんが、データにエラーがあります。もう一度リクエストしてみます。" ); アラート( err.name ); アラート( err.メッセージ ); }
ここではメッセージを表示するためだけにcatch
ブロックを使用していますが、それ以外にも、新しいネットワーク リクエストを送信したり、訪問者に代替案を提案したり、エラーに関する情報をログ機能に送信したりすることもできます。ただ死ぬよりずっといい。
json
構文は正しいが、必要なname
プロパティがない場合はどうなるでしょうか?
このような:
let json = '{ "年齢": 30 }'; // 不完全なデータ 試す { let user = JSON.parse(json); // <-- エラーなし アラート( ユーザー名 ); // 名前はありません! } キャッチ (エラー) { alert( "実行されません" ); }
ここでJSON.parse
正常に実行されますが、 name
が存在しないと実際にはエラーになります。
エラー処理を統一するには、 throw
演算子を使用します。
throw
演算子はエラーを生成します。
構文は次のとおりです。
throw <エラーオブジェクト>
技術的には、あらゆるものをエラー オブジェクトとして使用できます。これは数値や文字列のようなプリミティブでも構いませんが、できればname
とmessage
プロパティを持つオブジェクトを使用することをお勧めします (組み込みエラーとある程度の互換性を保つため)。
JavaScript には、 Error
、 SyntaxError
、 ReferenceError
、 TypeError
などの標準エラー用の組み込みコンストラクターが多数あります。これらを使用してエラー オブジェクトを作成することもできます。
それらの構文は次のとおりです。
let error = new Error(メッセージ); // または let error = new SyntaxError(message); let error = new ReferenceError(メッセージ); // ...
組み込みエラー (オブジェクトではなく、エラーのみ) の場合、 name
プロパティはコンストラクターの名前とまったく同じです。そしてmessage
引数から取得されます。
例えば:
let error = new Error("何かが起こる o_O"); アラート(エラー.名前); // エラー アラート(エラー.メッセージ); // 何かが起こる o_O
JSON.parse
どのような種類のエラーを生成するかを見てみましょう。
試す { JSON.parse("{ 悪い json o_O }"); } キャッチ (エラー) { アラート(err.name); // 構文エラー アラート(err.メッセージ); // JSON の位置 2 に予期しないトークン b があります }
ご覧のとおり、これはSyntaxError
です。
この場合、ユーザーはname
持っている必要があるため、 name
が存在しないとエラーになります。
それでは、投げてみましょう:
let json = '{ "年齢": 30 }'; // 不完全なデータ 試す { let user = JSON.parse(json); // <-- エラーなし if (!user.name) { throw new SyntaxError("不完全なデータ: 名前がありません"); // (*) } アラート( ユーザー名 ); } キャッチ (エラー) { alert( "JSON エラー: " + err.message ); // JSON エラー: 不完全なデータ: 名前がありません }
行(*)
では、 throw
演算子は、JavaScript 自体が生成するのと同じ方法で、指定されたmessage
を含むSyntaxError
を生成します。 try
の実行はただちに停止し、制御フローはcatch
にジャンプします。
現在、 catch
、 JSON.parse
とその他のケースの両方のすべてのエラー処理を 1 か所で行うことができるようになりました。
上の例では、 try...catch
使用して不正なデータを処理しています。しかし、 try {...}
ブロック内で別の予期しないエラーが発生する可能性はありますか?この「不正なデータ」の問題だけでなく、プログラミング エラー (変数が定義されていない) などのようなものです。
例えば:
let json = '{ "年齢": 30 }'; // 不完全なデータ 試す { ユーザー = JSON.parse(json); // <-- ユーザーの前に「let」を置くのを忘れました // ... } キャッチ (エラー) { alert("JSON エラー: " + err); // JSON エラー: ReferenceError: ユーザーが定義されていません // (実際には JSON エラーはありません) }
もちろん、すべてが可能です!プログラマーは間違いを犯します。何十年にもわたって何百万人もの人々に使用されてきたオープンソース ユーティリティであっても、突然恐ろしいハッキングにつながるバグが発見されることがあります。
この例では、「不正なデータ」エラーをキャッチするためにtry...catch
が配置されています。ただし、その性質上、 catch
try
からのすべてのエラーを取得します。ここでは予期しないエラーが発生しますが、同じ"JSON Error"
メッセージが表示されます。これは間違いであり、コードのデバッグがさらに困難になります。
このような問題を回避するには、「再スロー」テクニックを使用できます。ルールは簡単です:
Catch は、知っているエラーのみを処理し、他のすべてのエラーを「再スロー」する必要があります。
「再スロー」テクニックは次のように詳しく説明できます。
Catch はすべてのエラーを取得します。
catch (err) {...}
ブロックでは、エラー オブジェクトerr
分析します。
処理方法がわからない場合は、 throw err
。
通常、 instanceof
演算子を使用してエラーの種類を確認できます。
試す { ユーザー = { /*...*/ }; } キャッチ (エラー) { if (err 参照エラーのインスタンス) { アラート('参照エラー'); // 未定義の変数へのアクセスの場合は「ReferenceError」 } }
err.name
プロパティからエラー クラス名を取得することもできます。すべてのネイティブエラーにはそれがあります。別のオプションは、 err.constructor.name
を読み取ることです。
以下のコードでは、 catch
SyntaxError
のみを処理するように再スローを使用しています。
let json = '{ "年齢": 30 }'; // 不完全なデータ 試す { let user = JSON.parse(json); if (!user.name) { throw new SyntaxError("不完全なデータ: 名前がありません"); } ブラブラ(); // 予期しないエラー アラート( ユーザー名 ); } キャッチ (エラー) { if (err 構文エラーのインスタンス) { alert( "JSON エラー: " + err.message ); } それ以外 { エラーをスローします。 // 再スロー (*) } }
catch
ブロックの内側から行(*)
でスローされるエラーはtry...catch
から「抜け落ち」、外側のtry...catch
構造 (存在する場合) によってキャッチされるか、スクリプトが強制終了されます。
そのため、 catch
ブロックは実際には、対処方法がわかっているエラーのみを処理し、他のエラーはすべて「スキップ」します。
以下の例は、このようなエラーがもう 1 レベルのtry...catch
によってどのようにキャッチされるかを示しています。
関数 readData() { let json = '{ "年齢": 30 }'; 試す { // ... ブラブラ(); // エラー! } キャッチ (エラー) { // ... if (!(err 構文エラーのインスタンス)) { エラーをスローします。 // 再スロー(対処方法がわかりません) } } } 試す { readData(); } キャッチ (エラー) { alert( "外部キャッチが取得されました: " + err ); // 捕まえた! }
ここで、 readData
SyntaxError
処理方法のみを知っていますが、外側のtry...catch
すべての処理方法を知っています。
待ってください、それだけではありません。
try...catch
構造には、もう 1 つのコード句、 finally
含まれる場合があります。
存在する場合は、どのような場合でも実行されます。
try
の後、エラーがなかった場合は、
catch
の後、エラーがあった場合。
拡張された構文は次のようになります。
試す { ... コードを実行してみます ... } キャッチ (エラー) { ...エラーを処理します ... } ついに { ... 常に実行 ... }
このコードを実行してみてください。
試す { アラート( '試し' ); if (confirm('エラーを起こしますか?')) BAD_CODE(); } キャッチ (エラー) { アラート( 'キャッチ' ); } ついに { アラート( '最後に' ); }
コードには 2 つの実行方法があります。
「Make an error?」に「Yes」と答えた場合は、 try -> catch -> finally
。
「いいえ」という場合は、 try -> finally
。
finally
節は、何かを開始し、結果がどのような場合でもそれを完了させたい場合によく使用されます。
たとえば、フィボナッチ数関数fib(n)
にかかる時間を測定したいとします。もちろん、実行前に測定を開始し、実行後に測定を終了することもできます。しかし、関数呼び出し中にエラーが発生した場合はどうなるでしょうか?特に、以下のコードのfib(n)
の実装は、負の数値または非整数の場合にエラーを返します。
finally
節は、何があっても測定を完了するのに最適な場所です。
これにより、 fib
の実行が成功した場合とエラーが発生した場合の両方の状況で時間が正しく測定されることがfinally
保証されます。
let num = +prompt("正の整数を入力してください?", 35) diff、結果をみましょう。 関数 fib(n) { if (n < 0 || Math.trunc(n) != n) { throw new Error("負の値であってはならず、また整数であってはなりません。"); } n <= 1 を返しますか? n : fib(n - 1) + fib(n - 2); } let start = Date.now(); 試す { 結果 = fib(数値); } キャッチ (エラー) { 結果 = 0; } ついに { diff = Date.now() - 開始; } alert(結果 || "エラーが発生しました"); alert( `実行には ${diff}ms` かかりました` );
prompt
に35
入力してコードを実行することで確認できます。コードは正常に実行され、 finally
try
が実行されます。そして-1
入力すると、即時にエラーが発生し、実行には0ms
かかります。どちらの測定も正しく行われています。
言い換えれば、関数はreturn
またはthrow
で終了する可能性がありますが、それは問題ではありません。両方の場合において、 finally
節が実行されます。
変数はtry...catch...finally
内でローカルです
上記のコードのresult
とdiff
変数はtry...catch
前に宣言されていることに注意してください。
それ以外の場合、 try
ブロックでlet
宣言すると、その内部でのみ表示されます。
finally
にreturn
finally
節は、 try...catch
からの終了に対して機能します。これには明示的なreturn
が含まれます。
以下の例では、 try
にreturn
があります。この場合、 finally
制御が外側のコードに戻る直前に実行されます。
関数 func() { 試す { 1 を返します。 } キャッチ (エラー) { /* ... */ } ついに { アラート( '最後に' ); } } アラート( func() ); // 最初に、finally からのアラートが動作し、次にこれが動作します
try...finally
catch
句を使用しないtry...finally
構文も便利です。ここでエラーを処理したくない (失敗させたくない) が、開始したプロセスが完了していることを確認したい場合にこれを適用します。
関数 func() { // 完了が必要な何か (測定など) を開始します 試す { // ... } ついに { // たとえ全員が死んでもそれを完了する } }
上記のコードでは、 catch
がないため、 try
内のエラーは常に抜け落ちます。ただし、実行フローが関数を離れる前にfinally
機能します。
環境固有の
このセクションの情報は、コア JavaScript の一部ではありません。
try...catch
の外側で致命的なエラーが発生し、スクリプトが停止したと想像してみましょう。プログラミングエラーか何か他のひどいことのように。
このような出来事に対処する方法はありますか?エラーをログに記録したり、ユーザーに何かを表示したりする必要がある場合があります (通常、ユーザーにはエラー メッセージは表示されません)。
仕様には何もありませんが、非常に便利なため、通常は環境によって提供されます。たとえば、Node.js にはそのためのprocess.on("uncaughtException")
があります。また、ブラウザでは、特別な window.onerror プロパティに関数を割り当てることができ、これはキャッチされなかったエラーの場合に実行されます。
構文:
window.onerror = function(メッセージ、URL、行、列、エラー) { // ... };
message
エラーメッセージ。
url
エラーが発生したスクリプトの URL。
line
、 col
エラーが発生した行番号と列番号。
error
エラーオブジェクト。
例えば:
<スクリプト> window.onerror = function(メッセージ、URL、行、列、エラー) { alert(`${message}n ${url}` の ${line}:${col}); }; 関数 readData() { badFunc(); // おっと、何か問題が発生しました! } readData(); </script>
グローバル ハンドラーwindow.onerror
の役割は通常、スクリプトの実行を回復することではなく、プログラミング エラーの場合はおそらく不可能ですが、開発者にエラー メッセージを送信することです。
https://errorception.com や https://www.muscula.com など、そのような場合にエラー ログを提供する Web サービスもあります。
それらは次のように機能します。
サービスに登録し、ページに挿入する JS (またはスクリプト URL) をサービスから取得します。
この JS スクリプトはカスタムwindow.onerror
関数を設定します。
エラーが発生すると、それに関するネットワーク リクエストがサービスに送信されます。
サービス Web インターフェイスにログインすると、エラーが表示されます。
try...catch
コンストラクトを使用すると、実行時エラーを処理できます。文字通り、コードの実行を「試行」し、コード内で発生する可能性のあるエラーを「キャッチ」することができます。
構文は次のとおりです。
試す { // このコードを実行します } キャッチ (エラー) { // エラーが発生した場合は、ここにジャンプします // err はエラー オブジェクトです } ついに { // try/catch の後にいずれの場合も実行します }
catch
セクションやfinally
がない場合があるため、短い構造のtry...catch
およびtry...finally
も有効です。
エラー オブジェクトには次のプロパティがあります。
message
– 人間が読めるエラー メッセージ。
name
– エラー名を含む文字列 (エラー コンストラクター名)。
stack
(非標準ですが十分にサポートされています) – エラー作成時のスタック。
エラー オブジェクトが必要ない場合は、 catch (err) {
の代わりにcatch {
を使用して省略できます。
throw
演算子を使用して独自のエラーを生成することもできます。技術的には、 throw
の引数は何でも構いませんが、通常は組み込みのError
クラスを継承するエラー オブジェクトです。エラーの拡張については、次の章で詳しく説明します。
再スローはエラー処理の非常に重要なパターンです。通常、 catch
ブロックは特定のエラー タイプの処理方法を予期しており、その処理方法を知っているため、未知のエラーを再スローする必要があります。
try...catch
がない場合でも、ほとんどの環境では、「フォールアウト」エラーをキャッチする「グローバル」エラー ハンドラーをセットアップできます。ブラウザ内では、 window.onerror
です。
重要度: 5
2 つのコード部分を比較します。
最初のものは、 try...catch
の後に、 finally
使用してコードを実行します。
試す { 仕事仕事 } キャッチ (エラー) { エラーを処理する } ついに { 作業スペースを掃除する }
2 番目のフラグメントはtry...catch
の直後にクリーニングを配置します。
試す { 仕事仕事 } キャッチ (エラー) { エラーを処理する } 作業スペースを掃除する
エラーがあったかどうかは関係なく、作業後のクリーンアップは必ず必要です。
ここで、 finally
使用することに利点はありますか、それとも両方のコードフラグメントが等しいでしょうか?そのような利点がある場合は、重要なときに例を示してください。
関数内のコードを見ると、違いが明らかになります。
try...catch
の「ジャンプアウト」がある場合、動作は異なります。
たとえば、 try...catch
内にreturn
ある場合です。 finally
節は、 try...catch
が終了した場合でも、 return
ステートメントを介した場合でも機能します。 try...catch
が完了した直後、呼び出し元のコードが制御を取得する前です。
関数 f() { 試す { アラート('開始'); 「結果」を返します。 } キャッチ (エラー) { /// ... } ついに { アラート('クリーンアップ!'); } } f(); // 掃除!
…または、次のようにthrow
があるとき:
関数 f() { 試す { アラート('開始'); throw new Error("エラー"); } キャッチ (エラー) { // ... if("エラーを処理できません") { エラーをスローします。 } } ついに { アラート('クリーンアップ!') } } f(); // 掃除!
finally
ここでのクリーンアップが保証されます。コードをf
の最後に置くだけでは、このような状況では実行されません。