JavaScriptエラー処理ガイド

間違いは良いです。 本書の著者である翻訳者は、このアイデアを誰もが知っていると確信していると語っています。 一見、エラーは怖いようです。 何らかの損失を伴う場合があります。 公の場で犯した間違いは、それを犯した人の権威を傷つけます。 しかし、間違いを犯すと、私たちはそれらから学びます。つまり、次に誤って振る舞った状況に次回入ったときに、必要に応じてすべてを行います。







上記では、人々が日常生活で犯す過ちについて話しました。 プログラミングのエラーは別のものです。 エラーメッセージは、コードの改善に役立ちます。プロジェクトのユーザーに何か問題が発生したことを通知し、エラーが発生しないように動作をユーザーに伝えることができます。



このJavaScriptエラー処理資料は、3つの部分に分かれています。 最初に、JavaScriptのエラー処理システムの概要を説明し、エラーオブジェクトについて説明します。 その後、サーバーコードで発生するエラー(特にNode.js + Express.jsバンドルを使用する場合)で何をすべきかという質問に対する答えを探します。 次に、React.jsでのエラー処理について説明します。 ここで検討されるフレームワークは、非常に人気があるため選択されます。 ただし、ここで説明するエラーの処理の原則は普遍的であるため、ExpressとReactを使用しなくても、学んだことを作業ツールに簡単に適用できます。



この資料で使用されているデモプロジェクトのコードは、 このリポジトリにあります。



1. JavaScriptのエラーとそれらを扱う普遍的な方法



コードに問題が発生した場合は、次の構成を使用できます。



throw new Error('something went wrong')
      
      





このコマンドの実行中に、このオブジェクトでErrorオブジェクトのインスタンスが作成され、例外が生成されます(または、「スロー」と呼ばれます)。 throwステートメントは、任意の式を含む例外をスローできます。 この場合、エラーを処理するための対策が講じられていない場合、スクリプトの実行は停止します。



通常、初心者のJSプログラマーはthrow



ステートメントを使用しません。 通常、言語ランタイムまたはサードパーティライブラリによってスローされた例外が発生します。 これが発生すると、 ReferenceError: fs is not defined



ようなものがコンソールに入りReferenceError: fs is not defined



、プログラムの実行は停止します。



▍オブジェクトエラー



Error



オブジェクトのインスタンスには、使用できるいくつかのプロパティがあります。 私たちが興味を持つ最初のプロパティはmessage



です。 これは、引数としてエラーコンストラクターに渡すことができる行を取得する場所です。 たとえば、次の例は、 Error



オブジェクトのインスタンスの作成と、 message



プロパティにアクセスしてデザイナーが渡した文字列のコンソールへの出力を示しています。



 const myError = new Error('please improve your code') console.log(myError.message) // please improve your code
      
      





非常に重要なオブジェクトの2番目のプロパティは、エラースタックのトレースです。 これはstack



プロパティです。 これに目を向けると、コールスタック(エラー履歴)を表示できます。これは、プログラムの誤動作を引き起こした一連の操作を示しています。 特に、これにより、どのファイルに不良コードが含まれているかを理解し、エラーにつながった関数呼び出しのシーケンスを確認できます。 stack



プロパティにアクセスすることで表示できるものの例を次に示します。



 Error: please improve your code at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
      
      





ここで、上部にはエラーメッセージがあり、その後に実行がエラーの原因となったコードのセクションが示され、この失敗したセクションが呼び出された場所が説明されています。 これは、エラーコードフラグメントに関して「最も遠い」まで続きます。



▍生成とエラー処理



Error



オブジェクトのインスタンスを作成する、つまり、 new Error()



という形式のコマンドを実行しても、特別な結果は生じません。 エラーを生成するthrow



ステートメントを適用すると、興味深いことが起こり始めます。 既に述べたように、このようなエラーが処理されない場合、スクリプトの実行は停止します。 同時に、 throw



演算子がプログラマーによって使用されたかどうか、特定のライブラリーまたは言語ランタイム(ブラウザーまたはNode.js)でエラーが発生したかどうかに違いはありません。 さまざまなエラー処理シナリオについて話しましょう。



▍建設しよう...キャッチ



try...catch



は、しばしば忘れられるエラーを処理する最も簡単な方法です。 ただし、今日では、 async/await



構造のエラーを処理するために使用できるため、以前よりもはるかに集中的に使用されasync/await



ます。



このブロックは、同期コードで発生するエラーを処理するために使用できます。 例を考えてみましょう。



 const a = 5 try {   console.log(b) //  b   -   } catch (err) {   console.error(err) //          } console.log(a) //    ,   
      
      





この例で、失敗したconsole.log(b)



コマンドをtry...catch



で囲まなかった場合、スクリプトは停止します。



▍最後にブロック



エラーが発生したかどうかに関係なく、一部のコードを実行する必要がある場合があります。 これを行うには、 try...catch



コンストラクトで3番目のオプションのfinally



ブロックを使用できます。 多くの場合、その使用はtry...catch



直後に来るコードと同等try...catch



が、状況によっては便利になる場合があります。 以下にその使用例を示します。



 const a = 5 try {   console.log(b) //  b   -   } catch (err) {   console.error(err) //          } finally {   console.log(a) //        }
      
      





非同期メカニズム-コールバック



JavaScriptでプログラミングするときは、非同期で実行されるコードのセクションに常に注意を払う必要があります。 非同期関数があり、その中にエラーが発生した場合、スクリプトは引き続き実行されます。 JSの非同期メカニズムがコールバックを使用して実装される場合(これはお勧めしません)、対応するコールバック(コールバック関数)は通常2つのパラメーターを受け取ります。 これは、エラーおよびresult



含む可能性のあるerr



パラメーターのようなもので、非同期操作の結果を伴います。 次のようになります。



 myAsyncFunc(someInput, (err, result) => {   if(err) return console.error(err) //           console.log(result) })
      
      





コールバックでエラーが発生した場合、エラーパラメータとして表示されます。 それ以外の場合、このパラメーターは値undefined



またはnull



を取得しnull



err



何かerr



あることが判明した場合、この例ではreturn



コマンドを使用するif...else



if...else



を使用してelse



ブロックにコマンドを配置し、非同期操作の結果を処理するため、これに反応することが重要です。 エラーが発生した場合、結果、つまりこの場合は値undefined



持つ可能性のあるresult



パラメーターを処理する可能性を排除することです。 この値を操作すると、たとえば、オブジェクトが含まれていると想定される場合、それ自体がエラーを引き起こす可能性があります。 これは、 result.data



コンストラクトなどを使用しようとしたときに発生します。



synchronous非同期メカニズム-約束



JavaScriptで非同期操作を実行するには、コールバックではなくプロミスを使用することをお勧めします。 ここでは、コードの可読性の向上に加えて、より高度なエラー処理メカニズムがあります。 つまり、promiseを使用する場合、コールバック関数に分類される可能性のあるエラーオブジェクトを気にする必要はありません。 この目的のために、特別なcatch



ブロックが提供されています。 前のpromiseで発生したすべてのエラー、または前のcatch



後のコードで発生したすべてのエラーをインターセプトしcatch



。 処理するcatch



ブロックを持たないプロミスでエラーが発生した場合、スクリプトの実行は停止しませんが、エラーメッセージは特に読みにくいことに注意してください。



 (node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
      
      





その結果、Promiseを使用する場合は、いつでもcatch



を使用することをお勧めできcatch



。 例を見てみましょう。



 Promise.resolve(1)   .then(res => {       console.log(res) // 1       throw new Error('something went wrong')       return Promise.resolve(2)   })   .then(res => {       console.log(res) //        })   .catch(err => {       console.error(err) //  ,     ,         return Promise.resolve(3)   })   .then(res => {       console.log(res) // 3   })   .catch(err => {       //      ,      -        console.error(err)   })
      
      





synchronous非同期メカニズムとtry ... catch



JavaScriptにasync/await



コンストラクトが表示された後、エラーを処理する従来の方法に戻りました。 このアプローチでエラーを処理するのは非常に簡単で便利です。 例を考えてみましょう。



 ;(async function() {   try {       await someFuncThatThrowsAnError()   } catch (err) {       console.error(err) //       }   console.log('Easy!') //   })()
      
      





このアプローチでは、非同期コードのエラーは同期の場合と同じ方法で処理されます。 その結果、必要に応じて、1つのcatch



、より広範囲のエラーを処理できるようになりました。



2.サーバーコードでのエラーの生成と処理



エラーを処理するためのツールが用意できたので、実際の状況でそれらを使用して何ができるかを見てみましょう。 エラーを生成して正しく処理することは、サーバー側プログラミングの重要な側面です。 エラーを処理するには、さまざまなアプローチがあります。 ここでは、フロントエンドまたはサーバーAPIを使用するメカニズムに便利に渡されるError



オブジェクトとエラーコードのインスタンスに独自のコンストラクターを使用するアプローチを示します。 特定のプロジェクトのバックエンドがどのように構成されているかは、実際には問題ではありません。どのアプローチでも、エラーの処理に関して同じアイデアを使用できるからです。



ルーティングを担当するサーバーフレームワークとして、Express.jsを使用します。 効果的なエラー処理システムを編成するために必要な構造について考えてみましょう。 必要なものは次のとおりです。



  1. 普遍的なエラー処理は、エラーを処理するのに適した基本的なメカニズムです。その間、 Something went wrong, please try again or contact us



    なかったというメッセージSomething went wrong, please try again or contact us



    このシステムは特にインテリジェントではありませんが、少なくとも何かがうまくいかなかったことをユーザーに知らせることができます。 このようなメッセージは、「無限ダウンロード」またはそのようなものよりもはるかに優れています。
  2. 特定のエラーの処理-不適切なシステム動作の原因に関する詳細情報をユーザーに通知し、問題の対処方法に関する具体的なアドバイスをユーザーに提供できるメカニズム。 たとえば、これは、ユーザーがサーバーに送信するリクエストにいくつかの重要なデータがないこと、またはデータベースに特定のレコードが既に追加されているなどに関連する場合があります。


errorエラーオブジェクトの独自のコンストラクターの開発



ここでは、標準のError



クラスを使用して拡張します。 JavaScriptで継承メカニズムを使用するのは危険ですが、この場合、これらのメカニズムは非常に便利です。 なぜ継承が必要なのですか? 実際のところ、コードを便利にデバッグするには、エラースタックのトレースに関する情報が必要です。 標準のError



クラスを拡張すると、余分な労力なしでスタックをトレースできます。 独自のエラーオブジェクトに2つのプロパティを追加します。 1つ目はcode



プロパティで、これはerr.code



形式の構造を使用してアクセスできます。 2番目はstatus



プロパティです。 これは、アプリケーションのクライアント部分に送信される予定のHTTPステータスコードを記録します。



これは、コードがモジュールとして設計されているCustomError



クラスの外観です。



 class CustomError extends Error {   constructor(code = 'GENERIC', status = 500, ...params) {       super(...params)       if (Error.captureStackTrace) {           Error.captureStackTrace(this, CustomError)       }       this.code = code       this.status = status   } } module.exports = CustomError
      
      





▍ルーティング



エラーオブジェクトを使用する準備ができたので、ルート構造を構成する必要があります。 前述のように、エラー処理に対する統一されたアプローチを実装する必要があります。これにより、すべてのルートのエラーを等しく処理できます。 デフォルトでは、Express.jsフレームワークはそのようなスキームを完全にはサポートしていません。 実際には、すべてのルートがカプセル化されています。



この問題に対処するために、独自のルートハンドラを実装し、通常の関数の形式でルートロジックを定義できます。 このアプローチのおかげで、ルート関数(または他の関数)がエラーをスローした場合、それはルートハンドラーに分類され、アプリケーションのクライアント部分に渡すことができます。 サーバーでエラーが発生した場合、JSON-APIがこれに使用されると想定して、次の形式でフロントエンドにエラーを転送する予定です。



 {   error: 'SOME_ERROR_CODE',   description: 'Something bad happened. Please try again or contact support.' }
      
      





この段階で何が起こっているのか理解できない場合は、心配しないでください。読み続け、議論されていることを試してみてください。徐々に理解していきます。 実際、コンピュータートレーニングに関しては、一般的なアイデアが最初に議論され、次に詳細への移行が実行されるときに、ここでは「トップダウン」アプローチが使用されます。



これがルートハンドラコードの外観です。



 const express = require('express') const router = express.Router() const CustomError = require('../CustomError') router.use(async (req, res) => {   try {       const route = require(`.${req.path}`)[req.method]       try {           const result = route(req) //    route           res.send(result) //   ,     route       } catch (err) {           /*                ,    route             */           if (err instanceof CustomError) {               /*                   -                                  */               return res.status(err.status).send({                   error: err.code,                   description: err.message,               })           } else {               console.error(err) //                  //   -                   return res.status(500).send({                   error: 'GENERIC',                   description: 'Something went wrong. Please try again or contact support.',               })           }       }   } catch (err) {       /*          ,    ,  ,            ,  ,          ,                       */       res.status(404).send({           error: 'NOT_FOUND',           description: 'The resource you tried to access does not exist.',       })   } }) module.exports = router
      
      





コード内のコメントがそれを非常によく説明していると信じています。 それらのコードを読むことは、その後のコードの説明よりも便利であることを願っています。



ルートファイルを見てみましょう。



 const CustomError = require('../CustomError') const GET = req => {   //       return { name: 'Rio de Janeiro' } } const POST = req => {   //       throw new Error('Some unexpected error, may also be thrown by a library or the runtime.') } const DELETE = req => {   //  ,      throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.') } const PATCH = req => {   //      CustomError   try {       //   -        throw new Error('Some internal error')   } catch (err) {       console.error(err) //    ,           throw new CustomError(           'CITY_NOT_EDITABLE',           400,           'The city you are trying to edit is not editable.'       )   } } module.exports = {   GET,   POST,   DELETE,   PATCH, }
      
      





これらの例では、クエリ自体は何も実行されません。 エラーの発生について、さまざまなシナリオを単純に考慮します。 したがって、たとえば、 GET /city



リクエストはconst GET = req =>...



関数const GET = req =>...



に分類され、 POST /city



リクエストはconst POST = req =>...



関数const POST = req =>...



分類されます。 このスキームは、クエリパラメータを使用する場合にも機能します。 たとえば、フォームGET /city?startsWith=R



一般的に、エラーを処理する際、フロントエンドは、再試行またはサーバー所有者への連絡の申し出のみを含む一般エラー、または問題に関する詳細情報を含むCustomError



コンストラクターを使用して生成されるエラーのいずれかを取得することが実証されています。

一般的なエラーデータは、次の形式でアプリケーションのクライアント部分に送られます。



 {   error: 'GENERIC',   description: 'Something went wrong. Please try again or contact support.' }
      
      





CustomError



コンストラクターは次のように使用されます。



 throw new CustomError('MY_CODE', 400, 'Error description')
      
      





これにより、次のJSONコードがフロントエンドに渡されます。



 {   error: 'MY_CODE',   description: 'Error description' }
      
      





アプリケーションのサーバー部分で徹底的に作業したので、役に立たないエラーログはクライアント部分に分類されなくなりました。 代わりに、クライアントは何がうまくいかなかったかに関する有用な情報を受け取ります。



ここにあるコードを含むリポジトリがここにあることを忘れないでください。 ダウンロードして試してみて、必要に応じてプロジェクトのニーズに合わせて調整してください。



3.クライアントでエラーを処理する



次に、フロントエンドに関するエラー処理システムの3番目の部分について説明します。 ここでは、まずアプリケーションのクライアント部分で発生したエラーを処理する必要があり、次にサーバーで発生したエラーについてユーザーに通知する必要があります。 まず、サーバーエラー情報を表示します。 既に述べたように、この例ではReactライブラリーが使用されます。



errorエラー状態をアプリケーション状態で保存する



他のデータと同様に、エラーとエラーメッセージは変更される可能性があるため、コンポーネントの状態にすることは理にかなっています。 コンポーネントがマウントされると、エラーデータがリセットされるため、ユーザーが最初にページを表示したときにエラーメッセージは表示されません。



次に対処するのは、同じタイプのエラーを同じスタイルで表示する必要があるということです。 サーバーと同様に、ここでは3種類のエラーを区別できます。



  1. グローバルエラー-このカテゴリには、サーバーから発生する一般的な性質のエラーメッセージ、または他の同様の状況でユーザーがシステムにログインしていない場合に発生するエラーなどが含まれます。
  2. アプリケーションのサーバー側で生成される特定のエラー-これには、サーバーから報告されるエラーが含まれます。 たとえば、ユーザーがログインしてユーザー名とパスワードをサーバーに送信しようとしたときに、パスワードが間違っていることをサーバーが通知した場合、同様のエラーが発生します。 そのようなことは、アプリケーションのクライアント部分ではチェックされないため、そのようなエラーに関するメッセージはサーバーから送信されます。
  3. アプリケーションのクライアント部分によって生成された特定のエラー。 このようなエラーの例は、対応するフィールドに入力された無効なメールアドレスに関するメッセージです。


2番目と3番目のタイプのエラーは非常によく似ており、1レベルのコンポーネントの状態ストアを使用して操作できます。 彼らの主な違いは、それらが異なるソースから来ていることです。 以下では、コードを分析して、それらの使用を検討します。



Reactでアプリケーションの状態を管理するために組み込みシステムを使用しますが、必要に応じて、MobXやReduxなどの状態を管理するための特別なソリューションを使用することもできます。



▍グローバルエラー



通常、このようなエラーメッセージは、ステータスとともに最上位のコンポーネントに保存されます。 これらは、静的ユーザーインターフェイス要素に表示されます。 これは、画面上部の赤いボックス、モーダルウィンドウ、またはその他のものです。 実装は特定のプロジェクトに依存します。 これがエラーメッセージの表示です。









グローバルエラーメッセージ



ここで、 Application.js



ファイルに保存されているコードを見てください。



 import React, { Component } from 'react' import GlobalError from './GlobalError' class Application extends Component {   constructor(props) {       super(props)       this.state = {           error: '',       }       this._resetError = this._resetError.bind(this)       this._setError = this._setError.bind(this)   }   render() {       return (           <div className="container">               <GlobalError error={this.state.error} resetError={this._resetError} />               <h1>Handling Errors</h1>           </div>       )   }   _resetError() {       this.setState({ error: '' })   }   _setError(newError) {       this.setState({ error: newError })   } } export default Application
      
      





ご覧のように、この状態ではApplication.js



にエラーデータを保存する場所があります。 , .



GlobalError



, x



, . GlobalError



( GlobalError.js



).



 import React, { Component } from 'react' class GlobalError extends Component {   render() {       if (!this.props.error) return null       return (           <div               style={{                   position: 'fixed',                   top: 0,                   left: '50%',                   transform: 'translateX(-50%)',                   padding: 10,                   backgroundColor: '#ffcccc',                   boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',                   display: 'flex',                   alignItems: 'center',               }}           >               {this.props.error}                              <i                   className="material-icons"                   style={{ cursor: 'pointer' }}                   onClick={this.props.resetError}               >                   close               </font></i>           </div>       )   } } export default GlobalError
      
      





if (!this.props.error) return null



. , . . , , , . , , x



, - , .



, , _setError



Application.js



. , , , , ( error: 'GENERIC'



). ( GenericErrorReq.js



).



 import React, { Component } from 'react' import axios from 'axios' class GenericErrorReq extends Component {   constructor(props) {       super(props)       this._callBackend = this._callBackend.bind(this)   }   render() {       return (           <div>               <button onClick={this._callBackend}>Click me to call the backend</button>           </div>       )   }   _callBackend() {       axios           .post('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               }           })   } } export default GenericErrorReq
      
      





, . , , . . -, , -, UX-, , — .



▍ ,



, , , .













, . . . SpecificErrorReq.js



.



 import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component {   constructor(props) {       super(props)       this.state = {           error: '',       }       this._callBackend = this._callBackend.bind(this)   }   render() {       return (           <div>               <button onClick={this._callBackend}>Delete your city</button>               <InlineError error={this.state.error} />           </div>       )   }   _callBackend() {       this.setState({           error: '',       })       axios           .delete('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               } else {                   this.setState({                       error: err.response.data.description,                   })               }           })   } } export default SpecificErrorRequest
      
      





, , , x



. , , . , , — , , , . , , . , , , — .



▍,



, , , . , , - . .









,



SpecificErrorFrontend.js



, .



 import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component {   constructor(props) {       super(props)       this.state = {           error: '',           city: '',       }       this._callBackend = this._callBackend.bind(this)       this._changeCity = this._changeCity.bind(this)   }   render() {       return (           <div>               <input                   type="text"                   value={this.state.city}                   style={{ marginRight: 15 }}                   onChange={this._changeCity}               />               <button onClick={this._callBackend}>Delete your city</button>               <InlineError error={this.state.error} />           </div>       )   }   _changeCity(e) {       this.setState({           error: '',           city: e.target.value,       })   }   _validate() {       if (!this.state.city.length) throw new Error('Please provide a city name.')   }   _callBackend() {       this.setState({           error: '',       })       try {           this._validate()       } catch (err) {           return this.setState({ error: err.message })       }       axios           .delete('/api/city')           .then(result => {               //  -     ,               })           .catch(err => {               if (err.response.data.error === 'GENERIC') {                   this.props.setError(err.response.data.description)               } else {                   this.setState({                       error: err.response.data.description,                   })               }           })   } } export default SpecificErrorRequest
      
      







, , ( GENERIC



), , . , , , , , , , , . .



まとめ



Webアプリケーションでエラーを処理する方法を理解していただけたことを願っています。このようなものconsole.error(err)



は、デバッグ目的でのみ使用する必要があります。プログラマーが忘れたものは、本番環境に侵入しないでください。loglevelのような適切なライブラリを使用して、ロギングの問題の解決を簡素化します



親愛なる読者! プロジェクトのエラーをどのように処理しますか?






All Articles