如何从javascript错误对象中读取错误消息

unguejic  于 2023-03-28  发布在  Java
关注(0)|答案(2)|浏览(116)

有人能帮我解决下面的问题吗?
我正在通过下面的redux action进行后期呼叫。

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
            });
        })
}

上面的post调用失败时,返回带有错误消息的主体,示例如下

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

Question我想实现的是,为了抓取errors[]并将错误消息传递给组件,我遇到的麻烦是在返回的数组消息中访问error[]数组。我将在下面描述我所尝试的,它也可以在我上面发布的redux action方法中看到。
Try-1console.log('>>> in CATCH block, error is =>', error);仅产生Error
Try-2console.log('>>> in CATCH block, error name is =>', error.name);得到{"errors":[{"msg":"User already exist with email"}]},类型为string,因为返回的是text()return 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);产生object。
我试图实现的是,获取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')));
             }

希望上面的描述是清楚的,如果你需要更多的信息,请让我知道,我知道我错过了一些与错误对象工作的关键知识,有人能在这方面提供一些帮助吗:-)

gcxthw6b

gcxthw6b1#

标准格式HTTP payload恢复错误的抛出模式

你的redux操作确实可以通过HTTP工作。有时服务器会响应坏消息,并且似乎有一个标准化的格式来报告该消息。此外,有时你自己的代码会抛出。你想用与Error相关的控制结构来处理这两种问题。

异步Redux动作的基本模式

开始之前:你的操作标记为async,但是你仍然链接着.then.catch,让我们切换到async/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的示例,或者它的一个子类。两个主要原因是Error类做了一些有用的事情。(类似于捕获堆栈跟踪),并且由于两个故障源之一已经抛出了Error示例,因此如果您希望用一个代码路径处理这两种情况,则必须执行类似操作。
满足Error

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

我们已经知道,如果action中的代码崩溃,例如JSON.parse在响应体上失败,则会抛出Error,因此在这些情况下,我们不必做任何特殊的事情来将执行定向到catch路径上。
我们唯一需要负责的是检查HTTP响应是否包含类似于服务器的“标准错误有效负载”的内容(稍后将详细介绍),您的示例建议如下:

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

这是核心问题

这种处理必须是特殊的,因为没有javascript引擎认为它是一个错误只是接收一个HTTP有效载荷,可以解析为JSON,其中包含一个名为“错误”的关键字。(他们也不应该。)这种有效载荷模式只是一个自定义约定,由一些或所有的HTTP端点使用,* 你 * 交谈。
这并不是说这是一个坏主意。(我认为这是伟大的!)但这解释了为什么它必须做自定义:因为这个模式只是你的私人小东西,实际上并不特别,浏览器不会以你想要的特殊方式对待它。
我们的计划是这样的
1.发出请求,依靠try/catch来捕获我们的工具抛出的东西
1.如果我们得到一个不好的回答
1.检查有效载荷中以“标准格式”编码的错误;我把这种情况称为“API错误”
1.如果我们发现API错误,我们将创建并抛出我们自己的Error,使用API错误内容作为其消息
1.如果我们没有发现API错误,我们将把响应的原始正文文本作为错误消息
1.如果我们得到一个看起来不错的答案
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错误,则只是一条错误消息)

异常处理

你如何区分这些不同类型的失败?有两种方法:

  • 一些失败会抛出Error的特定子类,您可以使用error instanceof SomeErrorClass测试这些子类:
  • 如果JSON.stringify不能序列化其参数,则抛出TypeError(如果您在任何地方都有自定义.toJSON,则它也可以抛出任何抛出的东西)
  • fetch在无法访问Internet时抛出TypeError
  • JSON.parse在字符串无法解析时抛出SyntaxError(如果您使用自定义reviver,也会抛出这些错误)
  • Error或其子类的任何示例都有一个.message;你可以在特定的情况下测试这个字符串

你应该如何处理它们?
1.如果JSON.stringify崩溃了,那是因为你的数据连接错误。在这种情况下,你可能想做一些事情来提醒开发人员某些东西被破坏并帮助诊断问题:

  1. console.error(error)
    1.调度一些包含error.message的失败操作
    1.在屏幕上显示一般错误消息
    1.如果fetch抛出,您可以发送一个故障,向用户显示“修复您的wifi”警告。
    1.如果JSON.parse抛出,则服务器正在崩溃,您应该显示一条通用的错误消息。

有点复杂

这些都是基本的机制,但现在你面临着一个混乱的情况。让我们列出一些挑战:

  • 你可能已经注意到一个问题:“无互联网”将以与“循环数据”相同的方式呈现:一个被抛出的TypeError

  • 事实证明,JSON.stringify错误的精确文本取决于提供给该函数的实际值,因此您不能执行类似error.message === CONSTANT_STRINGIFY_ERROR_MESSAGE的操作。

  • 您可能没有服务器可以在API错误中发送的每个msg值的详尽列表。

那么,您应该如何区分正常服务器报告的问题与客户端错误、损坏的服务器与无法使用的用户数据之间的区别呢?
首先,我建议为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
}

每次使用不同的非数字x调用add时,error.message将不同:

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

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

这两种情况下的问题是,我为x提供了一个不可接受的值,但消息是不同的。这使得在运行时将这些情况组合在一起变得不必要的困难。我的示例甚至无法区分是x还是y违规!
这些麻烦通常会出现在你从本机代码和库代码中接收到的错误中,我的建议是,如果可以避免的话,不要在你自己的代码中重复它们。
我发现的最简单的补救方法就是总是使用静态字符串作为错误消息,并花些心思为自己建立约定。
一般有两种错误:

  • 我希望使用某些值是令人反感的
  • 我尝试某些操作失败

在第一种情况下,相关信息为:

  • 哪个数据点是坏的;我称之为“主题”
  • 为什么它是坏的,* 在一个词 *;我称之为“反对”

所有与令人反感的值相关的错误消息都应该包括两个数据点,并且以一种足够一致的方式来促进流控制,同时保持人类可以理解。理想情况下,您应该能够grep文字消息的代码库,以找到可能抛出错误的每个地方(这对维护有很大帮助)。
下面是我如何构建消息:

[objection] [topic]

通常有一组离散的异议

*缺失:未提供值
*未知:无法在DB中找到值和其他“坏键”问题
*不可用:值已被采用(例如用户名)
*禁止:有时,特定值是禁止的,尽管在其他方面很好(例如,没有用户可以具有用户名“root”)
*无效:被开发社区过度使用;作为最后的选择;为错误数据类型或语法上不可接受的值保留 * 独占 *(例如zipCode = '__!!@'

我会根据需要用更专门的反对意见来补充单个应用程序,但这个集合几乎出现在所有应用程序中。

主题几乎总是出现在抛出的代码块中的字面变量名。为了帮助调试,我认为不以任何方式转换变量名是非常重要的。

此系统会产生如下错误消息:

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

在第二种情况下,对于失败的操作,通常只有一个数据点:操作的名称(加上失败的事实)。我使用以下格式:

[operation] failed

一般来说,operation就是调用的例程:

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

这不是保持错误正确的唯一方法,但是这组约定确实使编写新的错误代码变得容易,而不必非常努力地思考,对异常做出明智的React,并定位大多数可能引发的错误的来源。

服务器不一致处理

最后一个主题:对于服务器来说,不一致的负载结构是很常见的,特别是在错误和成功的情况下。
通常情况下,两个端点会使用略有不同的包络对错误进行编码。有时,单个端点会针对不同的故障情况使用不同的包络。这通常不是故意的,但通常是现实。
你应该把所有不同风格的服务器抱怨强制到一个界面中,以免这些疯狂的东西泄露到你的应用程序的其他部分,而客户端/服务器边界的海岸是立即抛弃服务器怪异的最佳地方。如果你让这些东西泄露到你的应用程序的其他部分,不仅会让你发疯,但它会让你脆弱,因为它允许服务器在你的应用程序内部深处暴露错误,远离真实的的来源:违反了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类来 Package 这些东西的价值之一是,类构造函数提供了一种自然的方法来收集所有这些东西。

vmdwslir

vmdwslir2#

要在控制台中显示完整的错误消息,请执行以下操作:

console.error(error);

以编程方式获取错误的第一行:

// Newer browsers:
const message = `${error}`;

// Old and new browsers:
var message = error + '';

相关问题