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

use*_*497 19 javascript

有人可以帮我解决以下问题吗:-)

我正在通过 redux 操作进行后调用,如下所示。

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
            });
        })
}
Run Code Online (Sandbox Code Playgroud)

上面的失败后调用,返回带有错误消息的正文,示例如下

    {
    "errors": [
        {
            "msg": "User already exist with email"
        }
     ]
}
Run Code Online (Sandbox Code Playgroud)

问题 我想要实现的是,获取errors[]错误消息并将其传递给组件,我遇到的问题是访问error[]返回的数组消息中的数组。我将在下面描述我的尝试,它也可以在我上面发布的 redux 操作方法中看到。

Try-1 的 console.log('>>> in CATCH block, error is =>', error);结果只是Error

Try-2 console.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);生成对象时。

再次出现的问题我想要实现的是,获取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')));
             }
Run Code Online (Sandbox Code Playgroud)

希望上面的描述很清楚,如果您需要更多信息,请告诉我,我知道我缺少一些使用错误对象的重要知识,有人可以解释一下吗:-)

Tom*_*Tom 39

从标准格式 HTTP 有效负载恢复的抛出错误的模式

您的 redux 操作确实可以通过 HTTP 运行。有时,服务器会响应坏消息,并且服务器似乎使用标准化格式来报告该消息。另外,有时您自己的代码会抛出异常。您希望使用与 s 相关的控制结构来处理这两类问题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(/* ... */)
  })
}
Run Code Online (Sandbox Code Playgroud)

...到这个:

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
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们来谈谈错误。

错误基础知识

见面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 */ }
Run Code Online (Sandbox Code Playgroud)

throw几乎可以做任何事情。当您这样做时,执行会停止并立即跳转到最近的catch,在堆栈中一路搜索,直到找到一个或耗尽堆栈。无论它落在哪里,您提供的值都会throw成为接收到的参数catch(称为“异常”)。如果没有捕获到它,您的 JS 控制台会将其记录为“未捕获的异常”。

可以扔任何东西,但你应该扔什么?Error我认为你应该只抛出或其子类之一的实例。两个主要原因是该类Error做了一些有用的事情(例如捕获堆栈跟踪),并且因为您的两个失败来源之一已经将抛出Error实例,所以如果您希望使用单一代码路径。

见面Error

try {
  throw new Error('bad news')
  
} catch ( error ) {
  console.log(error.message)
  //> 'bad news'
}
Run Code Online (Sandbox Code Playgroud)

我们已经知道,Error如果操作中的代码崩溃,例如JSON.parse响应正文失败,则会抛出异常,因此在这些情况下,我们无需执行任何特殊操作即可直接执行到路径上catch

我们唯一需要负责的是检查 HTTP 响应是否包含类似于服务器的“标准错误负载”(稍后会详细介绍)的内容,您的示例建议是这样的:

{
  "errors": [
    {
      "msg": "ERROR CONTENT HERE"
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

这里是核心问题

这种处理必须是特殊的,因为没有任何 JavaScript 引擎会认为仅仅接收可解析为 JSON 且包含名为“errors”的键的 HTTP 有效负载是一个错误。(也不应该。)此有效负载模式仅仅是与之通信的部分或全部 HTTP 端点使用的自定义约定。

这并不是说这是一个坏主意。(我认为这很棒!)但这解释了为什么它必须自定义:因为这种模式只是您私人的小事情,实际上并不特别,不会让浏览器以您想要的特殊方式对待它。

所以这是我们的计划:

  1. 发出请求,依靠 try/catch 来捕获我们的工具抛出的东西
    • 如果我们得到的回应看起来很糟糕:
      1. 检查有效负载是否存在以“标准格式”编码的错误;我把这样的事情称为“API 错误”
      2. 如果我们发现 API 错误,我们将创建并抛出我们自己的Error,使用 API 错误内容作为其消息
      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
}
Run Code Online (Sandbox Code Playgroud)

以下是可以扔到该catch块中的内容:

  • JSON.parse应用于参数时抛出的任何内容
  • 任何抛出的东西fetch
  • if !response.ok,整个响应负载(或者如果负载包含 API 错误,则只是错误消息)

异常处理

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

  1. 有些失败会引发 的特定子类Error,您可以使用 进行测试error instanceof SomeErrorClass
    • JSON.stringifyTypeError如果它无法序列化其参数,则抛出 a (如果您.toJSON在任何地方有自定义,它也可以抛出任何抛出的内容)
    • fetchTypeError如果无法连接到互联网,则抛出 a
    • JSON.parseSyntaxError如果无法解析字符串,则抛出 a (如果您使用自定义恢复器,也会抛出这些错误)
  2. Error或其子类的任何实例都会有一个.message; 您可以针对特定情况测试该字符串

你应该如何处理它们?

  • 如果JSON.stringify爆炸,那是因为你的数据连接错误。在这种情况下,您可能需要执行一些操作来提醒开发人员某些内容已损坏并帮助诊断问题:
    1. console.error(error)
    2. 调度一些失败操作,包括error.message
    3. 在屏幕上显示一般错误消息
  • 如果fetch抛出异常,您可以发送一个故障,向用户显示“修复您的 wifi”警告。
  • 如果JSON.parse抛出异常,则服务器正在崩溃,您应该显示一条通用错误消息。

有点复杂

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

  • 您可能已经注意到一个问题:“无互联网”将以与“循环数据”相同的方式呈现:抛出一个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)
}

Run Code Online (Sandbox Code Playgroud)

那么,我们可以这样做:

// 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':
        // ...
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

其次,当抛出自己的错误时,切勿提供动态字符串作为消息

为理智的人提供的错误消息

考虑:

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
}
Run Code Online (Sandbox Code Playgroud)

每次add使用不同的非数字调用时xerror.message都会有所不同:

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

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

这两种情况的问题是我为 提供了不可接受的值x,但消息不同。这使得在运行时将这些情况分组在一起变得不必要的困难。我的例子甚至让人无法判断它是否x冒犯y了!

这些问题非常普遍地适用于您从本机代码和库代码中收到的错误。我的建议是,如果可以避免的话,不要在自己的代码中重复它们。

我发现的最简单的补救措施就是始终使用静态字符串作为错误消息,并花一些时间为自己建立约定。这就是我所做的。

一般有两种错误:

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

第一种情况,相关信息是:

  • 哪个数据点不好;我称之为“主题”
  • 一言以蔽之,为什么它不好;我称之为“反对”

与令人反感的值相关的所有错误消息都应该包括两个数据点,并且以足够一致的方式来促进流量控制,同时保持人类可以理解。理想情况下,您应该能够在grep文字消息的代码库中找到可能引发错误的每个位置(这对维护有很大帮助)。

以下是我构建消息的方式:

[objection] [topic]
Run Code Online (Sandbox Code Playgroud)

通常存在一组离散的反对意见

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

我根据需要为各个应用程序补充了更专业的反对意见,但这个集合几乎出现在所有内容中。

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

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

'missing lastName'
'unknown userId'
'unavailable player_color'
'forbidden emailAddress'
'invalid x'
Run Code Online (Sandbox Code Playgroud)

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

[operation] failed
Run Code Online (Sandbox Code Playgroud)

通常,操作是与调用完全相同的例程:

[objection] [topic]
Run Code Online (Sandbox Code Playgroud)

这不是纠正错误的唯一方法,但是这套约定确实可以轻松编写新的错误代码,而无需费力思考,智能地对异常做出反应,并找到大多数可能引发的错误的来源。

处理服务器不一致

最后一个主题:服务器在如何构建其有效负载方面不一致是很常见的,特别是在错误和成功的情况下。

通常,两个端点会使用稍微不同的包络对其错误进行编码。有时,单个端点会针对不同的故障情况使用不同的包络。这通常不是故意的,但往往是现实。

您应该将所有不同类型的服务器投诉强制到一个接口中,以免这种疯狂的行为泄漏到应用程序的其余部分,而客户端/服务器边界的岸边是立即抛弃服务器怪异的最佳位置。如果你让这些东西逃逸到你的应用程序的其余部分,它不仅会让你发疯,而且还会因为允许服务器在你的应用程序深处暴露错误而使你变得脆弱,远离真正的来源:违反的 API 合同。

支持各种信封的一种方法是getErrorMessageFromResponseBody为每个不同的信封添加额外的代码:

'missing lastName'
'unknown userId'
'unavailable player_color'
'forbidden emailAddress'
'invalid x'
Run Code Online (Sandbox Code Playgroud)

使用专用的 APIError 类来包装这些内容的价值之一是,类构造函数提供了一种自然的方式来收集所有这些内容。