JavaScript エラーオブジェクトからエラーメッセージを読み取る方法 質問する

JavaScript エラーオブジェクトからエラーメッセージを読み取る方法 質問する

誰か以下の質問について助けてくれませんか :-)

以下のように、redux アクションを通じて post 呼び出しを行っています。

export const addEmployee = ({ firstName, surname, contactNumber, email }) => async dispatch => {
const payloadBody = JSON.stringify({ firstName, surname, contactNumber, email });
    fetch('/api/users', { 
            method: 'POST', 
            body: payloadBody,
            headers: {
                'Content-Type': 'application/json'
            }
        })
        .then(response => {
            if (!response.ok) {
                return response.text()
                .then(text => { 
                    throw Error(text)
                });
            } else {
                dispatch(setAlert("New Employee added ", 'danger'));
            }
        })
        .catch(error => {
            console.log('>>> in CATCH block, error is =>', error);
            console.log('>>> in CATCH block, error name is =>', error.name);
            console.log('>>> in CATCH block, error message is =>', error.message);

            let allKeys = Object.getOwnPropertyNames(error);
            console.log(allKeys); 

            // const errors = [];
            // Object.keys(error.message).forEach(key => {
            //     console.log('>>> key are ', key)
            // })


            // const keys = Object.keys(error.message);
            // console.log(keys);

            // const errors = error.message['errors'];
            
            
            // const errors = error.response.data.errors;

            // if (errors) {
            //     errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
            // }

            dispatch({
                type: REGISTER_FAIL
            });
        })
}

上記のポスト呼び出しが失敗すると、エラーメッセージを含む本文が返されます。例を以下に示します。

    {
    "errors": [
        {
            "msg": "User already exist with email"
        }
     ]
}

質問私が実現しようとしているのは、errors[]エラー メッセージを取得してコンポーネントに渡すことですが、error[]返された配列メッセージ内の配列にアクセスするときに問題が発生します。私が試みたことを以下に説明しますが、これは上記で投稿した redux アクション メソッドでも確認できます。

トライ1 console.log('>>> in CATCH block, error is =>', error);ちょうどError

トライ2 console.log('>>> in CATCH block, error name is =>', error.name);結果は、text()を返すので{"errors":[{"msg":"User already exist with email"}]}、typeofはこれです。stringreturn response.text().then(text => { throw Error(text) })

トライ3

json() として戻りreturn response.json().then(text => { throw Error(text) })console.log('>>> in CATCH block, error message is =>', error.message);オブジェクトを生成します。

もう一度質問です。私が達成しようとしているのは、errors[]エラーメッセージを取得して、以下のようなコンポーネントに渡すことです。

            const errors = error.message; // this is where I'd like to extract the error.

             if (errors) {
                 errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
             }

上記の説明が明確であることを願います。さらに情報が必要な場合はお知らせください。エラー オブジェクトの操作に関する重要な知識が不足していることは承知しています。どなたか、この点について説明していただけないでしょうか :-)

ベストアンサー1

標準形式の HTTP ペイロードから回復されたエラーをスローするパターン

Redux アクションは HTTP 経由で動作します。サーバーが悪いニュースで応答することがあり、サーバーがそのニュースを報告するために使用する標準化された形式があるようです。また、独自のコードがスローされることもあります。両方の種類の問題を、Errors に関連する制御構造で処理する必要があります。

非同期Reduxアクションの基本パターン

始める前に: アクションは とマークされていますasyncが、まだ と が連鎖しています.then。async .catch/await に切り替えて、これを変換しましょう:

export const addEmployee = (/*...*/) = async ( dispatch, getState ) => {
  fetch(/* ... */)
  .then(response => {
    return response.text()
    .then(text => { 
      // happy-path logic
      throw Error(text)
    })
  })
  .catch(error => {
    // sad-path logic
    dispatch(/* ... */)
  })
}

...このように:

export const addEmployee = (/*...*/) = async ( dispatch, getState ) => {
  try {
    let response = await fetch(/* ... */)
    let responseText = await response.text()
    // happy-path logic
    dispatch(/* ... */)
    return // a redux action should return something meaningful
    
  } catch ( error ) {
    // sad-path logic
    dispatch(/* ... */)
    return // a failed redux action should also return something meaningful
  }
}

さて、エラーについてお話しましょう。

エラーの基本

会うthrow:

try { throw 'mud'      } catch( exception ) { /* exception === 'mud' */ }
try { throw 5          } catch( exception ) { /* exception === 5     */ }
try { throw new Date() } catch( exception ) { /* exception is a Date */ }

throwほぼ何でもできます。実行すると、実行が停止し、最も近いcatchに即座にジャンプし、スタック全体を検索して、 が見つかるかスタックがなくなるまで続けます。どこに着地しても、 に指定した値が がthrow受け取る引数になりますcatch(「例外」と呼ばれます)。何もキャッチしない場合は、JS コンソールに「キャッチされない例外」として記録されます。

あなたできる何でも投げるが、何すべきError何をスローしますか? 、またはそのサブクラスのインスタンスのみをスローするべきだと思います。主な 2 つの理由はError、クラスがいくつかの便利な機能 (スタック トレースのキャプチャなど) を実行することと、2 つの失敗の原因の 1 つがすでにインスタンスをスローすることであるためError、単一のコード パスで両方を処理する場合は同様のことを行う必要があることです。

会うError:

try {
  throw new Error('bad news')
  
} catch ( error ) {
  console.log(error.message)
  //> 'bad news'
}

Errorアクション内のコードが失敗した場合、たとえばレスポンス本文で失敗した場合に がスローされることは既にわかっています。JSON.parseそのため、そのようなシナリオでパスに実行を誘導するために特別な操作を行う必要はありませんcatch

私たちが責任を持つ必要があるのは、HTTP 応答にサーバーの「標準エラー ペイロード」のようなものが含まれているかどうかを確認することだけです (これについては後で詳しく説明します)。サンプルでは、​​これが次のものであることが示されています。

{
  "errors": [
    {
      "msg": "ERROR CONTENT HERE"
    }
  ]
}

核心的な問題はこれだ

この処理は特別なものでなければならない。JavaScriptエンジンはこれをエラーとみなしません単にJSONとして解析でき、「errors」というキーを含むHTTPペイロードを受信するだけです。(そうすべきでもありません。)このペイロードパターンは、HTTPエンドポイントの一部またはすべてで使用されるカスタム規則にすぎません。あなたに話す。

悪いアイデアだと言っているわけではありません。(素晴らしいアイデアだと思います!)しかし、それはなぜこれはカスタムで行う必要があります。このパターンは単なるあなたの個人的な小さなものであり、実際にはブラウザがそれを望む特別な方法で処理するような特別なものではないためです。

これが私たちの計画です:

  1. リクエストを作成し、ツールによってスローされたものを捕捉するためにtry/catchを使用します
    • 悪いと思われる応答を受け取った場合:
      1. ペイロードを調べて「標準形式」でエンコードされたエラーを探します。私はこのようなエラーを「API エラー」と呼んでいます。
      2. APIエラーが見つかった場合、私たちは自分たちで創造し、投げますErrorAPIエラーの内容をメッセージとして使用します
      3. APIエラーが見つからない場合は、レスポンスの本文をそのままエラーメッセージとして扱います。
    • 良いと思われる回答が得られた場合:
      1. 良いニュース(および役立つデータ)を店舗に送信する

コードで表すと次のようになります。

export const addEmployee = ({
  firstName,
  surname,
  contactNumber,
  email
}) => async ( dispatch, getState ) => {
  const payloadBody = {
    firstName,
    surname,
    contactNumber,
    email
  }
  
  try {
    // step 1
    let response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payloadBody)
    })
    
    let responseText = await response.text()
    if (!response.ok) {
      // step 2
      let errorString = getErrorMessageFromResponseBody(responseText)
      throw new Error(errorString) // API errors get thrown here
    }
    
    // step 3
    let responseJson = JSON.parse(responseText)
    
    dispatch(setAlert('New Employee added', responseJson.user.name))
    
    /*
      A redux action should always returns something useful.
      addEmployee might return the `user` object that was created.
    */
    return responseJson.user
    
  } catch ( error ) {
    // all errors land here
    dispatch({
      type: REGISTER_FAIL,
      message: error.message
    })
    /*
      A failed redux action should always return something useful (unless you prefer to throw).
      For now, we'll return the reason for the failure.
    */
    return error.message
  }
}


function getErrorMessageFromResponseBody( string ) {
  let errorString = string
  
  try {
    let json = JSON.parse(string)
    if(json.errors) {
      errorString = json.errors[0].msg
    }
  } catch ( parseOrAccessError ) {}
  
  return errorString
}

catchこのブロックに投げられるものは次のとおりです:

  • JSON.parse引数に適用されたときにスローされるもの
  • 投げられたものfetch
  • の場合!response.ok、レスポンスペイロード全体(またはペイロードにAPIエラーが含まれている場合はエラーメッセージのみ)

例外処理

こうした異なる種類の失敗をどのように区別すればよいのでしょうか? 2 つの方法があります。

  1. いくつかの失敗は の特定のサブクラスをスローしますError。これは でテストできますerror instanceof SomeErrorClass
    • JSON.stringifyTypeError引数をシリアル化できない場合はをスローします(カスタムがある.toJSON場合は、スローするものもスローできます)
    • fetchTypeErrorインターネットに接続できない場合は
    • JSON.parseSyntaxError文字列を解析できない場合は をスローします(カスタム リバイバーを使用する場合は、それらのエラーもスローされます)
  2. またはそのサブクラスのインスタンスには;Errorが含まれます。.message特定のケースでその文字列をテストできます。

どのように対処すればよいでしょうか?

  • クラッシュした場合はJSON.stringify、データの配線が間違っているためです。その場合、何かが壊れていることを開発者に警告し、問題の診断に役立つような操作を行う必要があります。
    1. console.error(error)
    2. 障害アクションをディスパッチするerror.message
    3. 画面に一般的なエラーメッセージを表示する
  • スローされた場合はfetch、ユーザーに「Wi-Fi を修正してください」という警告を表示する失敗をディスパッチできます。
  • スローされた場合JSON.parse、サーバーがダウンしているため、一般的なエラー メッセージを表示する必要があります。

少し洗練された

これらは基本的な仕組みですが、ここで厄介な状況に直面します。いくつかの課題を挙げてみましょう。

  • すでに 1 つの問題に気づいているかもしれません。「インターネットなし」は「循環データ」と同じように表示されます。つまり、 がスローされますTypeError
  • エラーの正確なテキストはJSON.stringifyその関数に提供される実際の値によって決まるため、 のようなことはできませんerror.message === CONSTANT_STRINGIFY_ERROR_MESSAGE
  • msgサーバーが API エラーで送信できるすべての値の完全なリストがない可能性があります。

では、正常なサーバーによって報告された問題と、クライアント側のバグと、壊れたサーバーと、使用できないユーザー データの違いをどのように見分ければよいのでしょうか?

まず、API エラー用の特別なクラスを作成することをお勧めします。これにより、サーバーから報告された問題を信頼性の高い方法で検出できます。また、内部のロジックに適した場所が提供されますgetErrorMessageFromResponseBody

class APIError extends Error {}

APIError.fromResponseText = function ( responseText ) {
  // TODO: paste entire impl of getErrorMessageFromResponseBody
  let message = getErrorMessageFromResponseBody(responseText)
  return new APIError(message)
}

次に、次の操作を実行します。

// throwing
if (!response.ok) {
  // step 2
  throw APIError.fromResponseText(responseText)
}

// detecting
catch ( exception ) {
  if(exception instanceof APIError) {
    switch(APIError.message) {
      case 'User already exist with email':
        // special logic
        break
      case 'etc':
        // ...
    }
  }
}

第二に、自分自身のエラーを投げる場合、メッセージとして動的な文字列を提供しない

正気な人のためのエラーメッセージ

考慮する:

function add( x, y ) {
  if(typeof x !== 'number')
    throw new Error(x + ' is not a number')
  
  if(typeof y !== 'number')
    throw new Error(y + ' is not a number')
  
  return x + y
}

addが異なる非数値で呼び出されるたびにx、 はerror.message異なります。

add('a', 1)
//> 'a is not a number'

add({ species: 'dog', name: 'Fido' }, 1) 
//> '[object Object] is not a number'

どちらの場合も、 に不適切な値を指定しているのに、メッセージが異なっているという問題があります。そのため、実行時にこれらのケースをグループ化することが不必要に難しくなります。私の例では、問題が なのかxなのかさえ判断できません。xy

これらの問題は、ネイティブ コードやライブラリ コードから発生するエラーにほぼ当てはまります。避けられるのであれば、自分のコードでこれらの問題を繰り返さないようにすることをお勧めします。

私が見つけた最も簡単な解決策は、エラー メッセージには常に静的な文字列を使用し、自分自身で規則を確立することに少し注意を払うことです。私が行っていることは次のとおりです。

一般的に、エラーには 2 種類あります。

  • 私が使用したい値の一部は不適切です
  • 試みた操作が失敗しました

最初のケースでは、関連情報は次のとおりです。

  • どのデータポイントが悪いのか。私はこれを「トピック」と呼んでいます
  • なぜそれが悪いのか、一言で私はこれを「異議」と呼んでいます

不適切な値に関連するすべてのエラー メッセージには、両方のデータ ポイントを含める必要があります。また、フロー制御を容易にするのに十分な一貫性を保ちながら、人間が理解できる方法で含める必要があります。また、理想的には、grepリテラル メッセージのコードベースで、エラーが発生する可能性のあるすべての場所を見つけることができる必要があります (これはメンテナンスに非常に役立ちます)。

メッセージの作成方法は次のとおりです。

[objection] [topic]

通常、異議:

  • ない: 値が指定されませんでした
  • 未知: DB で値が見つからない、その他の「不正なキー」の問題
  • 利用不可: 値は既に使用されています (例: ユーザー名)
  • 禁断: 場合によっては、他の点では問題ないのに、特定の値が使用不可になることがあります (例: ユーザー名が「root」のユーザーは使用不可)
  • 無効: 開発者コミュニティによって過度に使用されている。最後の手段として扱う。予約済み排他的に間違ったデータ型または構文的に受け入れられない値(例zipCode = '__!!@'

必要に応じて、より専門的な異議を個々のアプリに追加しますが、このセットはほぼすべてのアプリに登場します。

トピックほとんどの場合、スローされたコード ブロック内に表示されるリテラル変数名です。デバッグを支援するために、変数名を一切変換しないことが非常に重要だと思います。

このシステムでは次のようなエラー メッセージが表示されます。

'missing lastName'
'unknown userId'
'unavailable player_color'
'forbidden emailAddress'
'invalid x'

2 番目のケースでは、失敗した操作の場合、通常はデータポイントは 1 つだけです。つまり、操作の名前 (および失敗したという事実) です。私は次の形式を使用します。

[operation] failed

原則として、手術呼び出されたルーチンとまったく同じです:

try {
  await API.updateUserProfile(newData)
  
} catch( error ) {
  // can fail if service is down
  if(error instanceof TypeError)
    throw new Error('API.updateUserProfile failed')
}

これはエラーを整理する唯一の方法ではありませんが、この一連の規則により、深く考えなくても新しいエラー コードを簡単に記述し、例外にインテリジェントに反応し、スローされる可能性のあるほとんどのエラーの原因を特定できるようになります。

サーバーの不整合の処理

最後のトピック: サーバーがペイロードの構造に関して一貫性がないことは、特にエラーの場合だけでなく成功の場合にもよくあることです。

多くの場合、2 つのエンドポイントはわずかに異なるエンベロープを使用してエラーをエンコードします。 場合によっては、1 つのエンドポイントが、異なる障害ケースに対して異なるエンベロープを使用することもあります。 これは通常意図的ではありませんが、多くの場合、現実に起こります。

この狂気がアプリケーションの他の部分に漏れ出す前に、サーバーのさまざまな苦情を単一のインターフェースに強制する必要があります。クライアント/サーバーの境界の岸は最高の場所サーバーの異常をすぐに排除します。その異常をアプリの残りの部分に漏らしてしまうと、気が狂ってしまうだけでなく、サーバーがアプリの奥深くでエラーを表面化させ、実際の原因である API 契約違反から遠く離れた場所にいるため、脆弱性が生まれます。

さまざまなエンベロープをサポートするには、getErrorMessageFromResponseBody異なるエンベロープごとに追加のコードを追加します。

function getErrorMessageFromResponseBody( string ) {
  let errorString = string
  
  /*
    "Format A"
    { errors: [{ msg: 'MESSAGE' }] }
    used by most endpoints
  */
  try { /*... */ } catch ( parseOrAccessError ) {}
  
  /*
    "Format B"
    { error: { message: 'MESSAGE' } }
    used by legacy TPS endpoint
  */
  try { /*... */ } catch ( parseOrAccessError ) {}
  
  /*
    "Format C"
    { e: CODE }
    used by bandwidth-limited vendor X
    use lookup table to convert CODE to a readable string
  */
  try { /*... */ } catch ( parseOrAccessError ) {}
  
  return errorString
}

これらをラップする専用の APIError クラスを持つことの価値の 1 つは、クラス コンストラクターがこれらすべてを収集する自然な方法を提供することです。

おすすめ記事