如何使用 Redux Thunk 处理 fetch() 响应中的错误?

Posted

技术标签:

【中文标题】如何使用 Redux Thunk 处理 fetch() 响应中的错误?【英文标题】:How to handle errors in fetch() responses with Redux Thunk? 【发布时间】:2016-09-01 20:55:04 【问题描述】:

我正在使用同构 fetch 发出 API 请求,并使用 Redux 来处理我的应用程序的状态。

我想通过触发 Redux 操作来处理互联网连接丢失错误和 API 错误。

我有以下(正在进行的/错误的)代码,但无法找出触发 Redux 操作的正确方法(而不是仅仅抛出错误并停止一切):

export function createPost(data = ) 

    return dispatch => 

        dispatch(requestCreatePost(data))

        return fetch(API_URL + data.type, 
            credentials: 'same-origin',
            method: 'post',
            headers: 
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'X-WP-Nonce': API.nonce
            ,
            body: JSON.stringify(Object.assign(, data, status: 'publish'))
        ).catch((err) => 

            //HANDLE WHEN HTTP ISN'T EVEN WORKING
            return dispatch => Promise.all([
                dispatch(type: PRE_FETCH_RESOURCES_FAIL, errorType: 'fatal', message:'Error fetching resources', id: h.uniqueId()),
                dispatch(type: PRE_CREATE_API_ENTITY_ERROR, errorType: 'fatal', id: h.uniqueId(), message: 'Entity error before creating')
            ])
        ).then((req) => 

            //HANDLE RESPONSES THAT CONSTITUTE AN ERROR (VIA THEIR HTTP STATUS CODE)
            console.log(req);
            if (!req || req.status >= 400) 
                return dispatch => Promise.all([
                    dispatch(type: FETCH_RESOURCES_FAIL, errorType: 'warning', message:'Error after fetching resources', id: h.uniqueId()),
                    dispatch(type: CREATE_API_ENTITY_ERROR, errorType: 'warning', id: h.uniqueId(), message: 'Entity error whilst creating')
                ])
            
            else 
                return req.json()
            
        ).then((json) => 
            var returnData = Object.assign(,json,
                type: data.type
            );
            dispatch(receiveCreatePost(returnData))
        )
    

如果我在 JS 控制台中故意禁用互联网连接,当我通过 console.log() 登录时(如上),它会输出以下内容: POST http://example.com/post net::ERR_INTERNET_DISCONNECTED(anonymous function) (dispatch) return Promise.all([dispatch( type: PRE_FETCH_RESOURCES_FAIL, errorType: 'fatal', message: 'Error fetching resources', id: _CBUtils2.default.uniqueId() ), dispatch( type:… cb_app_scripts.js?ver=1.0.0:27976 Uncaught (in promise) TypeError: req.json is not a function(…)

如果这是完全错误的,请原谅我,但我不想做任何事情,只是在出现错误时触发两个 Redux Actions(一个一般错误,一个特定于我们在错误发生时执行的操作) .

我正在努力实现的目标是否可能?

似乎(通过我的控制台日志记录)脚本的“then”部分仍在执行(因为它的内容是我的“catch”调度函数)..

【问题讨论】:

【参考方案1】:

解决方案只是(对于错误日志记录的两个实例)替换:

return dispatch => Promise.all([
    dispatch(type: PRE_FETCH_RESOURCES_FAIL, errorType: 'fatal', message:'Error fetching resources', id: h.uniqueId()),
    dispatch(type: PRE_CREATE_API_ENTITY_ERROR, errorType: 'fatal', id: h.uniqueId(), message: 'Entity error before creating')
])```

与:

return Promise.all([
    dispatch(type: PRE_FETCH_RESOURCES_FAIL, errorType: 'fatal', message:'Error fetching resources', id: h.uniqueId()),
    dispatch(type: PRE_CREATE_API_ENTITY_ERROR, errorType: 'fatal', id: h.uniqueId(), message: 'Entity error before creating'),
Promise.reject(err)
])

【讨论】:

Promise.allPromise.reject 都是不必要的。虽然您可以在技术上使其工作,但这会使代码比它需要的更复杂,并且没有任何用途。有关详细信息和重写建议,请参阅我的答案。【参考方案2】:

我对几件事感到困惑:

    为什么在调度两个同步动作时使用Promise.all?用type: PRE_FETCH_RESOURCES_FAIL, ... 之类的东西调用dispatch 不会返回Promise,所以Promise.all 是不必要的。 Promise.all() 仅在您调度的动作它们自己写成 thunk 动作创建者时才有用,这里不是这种情况。 return dispatch => ... 在动作创建者的一开始只需要一次。没有必要在catchthen 块中重复这一点——事实上,重复它会使内部代码根本不执行。这是一种将dispatch 注入顶层函数的方法,没有必要重复。 如果将then 放在catch 之后,即使在捕获到错误后它也会运行。这不是您想要的行为——在错误处理程序之后立即运行成功处理程序是没有意义的。您希望它们成为两个独立的代码路径。 次要命名 nitpick:您将响应称为“req”。应该是res

感觉就像您对 Redux Thunk 的工作原理有一个错误的心理模型,并试图将不同示例的各个部分组合在一起,直到成功为止。随机缩进也导致这段代码有点难以理解。

这在未来会很痛苦,所以我建议对 Redux Thunk 的作用、return dispatch => ... 的含义以及 Promises 如何融入图片有一个更完整的心智模型。我会推荐这个答案为in-depth introduction to Redux Thunk。

如果我们解决了这些问题,您的代码应该大致如下所示:

export function createPost(data = ) 
  return dispatch => 
    dispatch(requestCreatePost(data));

    return fetch(API_URL + data.type, 
      credentials: 'same-origin',
      method: 'post',
      headers: 
        'Accept': 'application/json',
        'Content-Type': 'application/json',
        'X-WP-Nonce': API.nonce
      ,
      body: JSON.stringify(Object.assign(, data, status: 'publish'))
    )
    // Try to parse the response
    .then(response =>
      response.json().then(json => (
        status: response.status,
        json
      )
    ))
    .then(
      // Both fetching and parsing succeeded!
      ( status, json ) => 
        if (status >= 400) 
          // Status looks bad
          dispatch(type: FETCH_RESOURCES_FAIL, errorType: 'warning', message:'Error after fetching resources', id: h.uniqueId()),
          dispatch(type: CREATE_API_ENTITY_ERROR, errorType: 'warning', id: h.uniqueId(), message: 'Entity error whilst creating')
         else 
          // Status looks good
          var returnData = Object.assign(, json, 
              type: data.type
          );
          dispatch(receiveCreatePost(returnData))
        
      ,
      // Either fetching or parsing failed!
      err => 
        dispatch(type: PRE_FETCH_RESOURCES_FAIL, errorType: 'fatal', message:'Error fetching resources', id: h.uniqueId()),
        dispatch(type: PRE_CREATE_API_ENTITY_ERROR, errorType: 'fatal', id: h.uniqueId(), message: 'Entity error before creating')
      
    );
  

【讨论】:

哇,感谢您的详细剖析。立即阅读 Redux Thunk 介绍。非常感谢! @Dan Abramov 如果我想将提取提取到一个单独的地方怎么办,连同“提取完全失败(比如 cors 或连接超时/拒绝)”捕获,但留下更具体的捕获那个行动。有可能吗? @Dan Abramov - 在这种情况下,我遇到了链接承诺的问题。它总是在链中触发已解决的函数而不是被拒绝的函数,当在 thunk 中它肯定被拒绝时,因为我的错误操作正在被调度。想法? 更新,我的错。我最终返回了一个包含我的 api 调用的承诺,它返回了承诺。由于正在处理承诺,因此我正在 .then() 退出链外的承诺,因此由于已经处理了错误,因此调用了成功。

以上是关于如何使用 Redux Thunk 处理 fetch() 响应中的错误?的主要内容,如果未能解决你的问题,请参考以下文章

使用 redux-thunk 取消之前的 fetch 请求

如何使用 redux-thunk 和 try-catch 处理多个调度结果?

React Redux thunk - 链接调度

在redux和redux thunk中使用异步调度传递参数

redux-thunk 是不是应该管理设备上的数据加载和保存?

如何在 Typescript 中使用 redux-thunk 使用 ThunkAction 正确键入 thunk?