最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Error handling redux-promise-middleware - Stack Overflow

programmeradmin1浏览0评论

I'm learning React, along with pretty much all the necessary technology around it all at once - so I often get tripped up by things I should probably know already.

I've encountered a problem when it es to error handling my async events. I've scoured the web and nothing really answers exactly what I'm looking for.

I'm currently using redux with redux-promise-middleware to handle the async actions, like this:

export function myFunc() {
  return {
    type: FETCH_FUNC,
    payload: new Promise((resolve, reject) => {
      fetch ('some/url/location/from/which/to/fetch')
        .then( response => {
          if (!response.ok){
            throw new Error(response);
            }
          resolve(response.json());
        }).catch(error => {
          reject(error);
        }),
    })
  };
}

There are two things here: first, the code works just fine when no errors are present. However, when I purposely create an error in the code the correct methods are firing but I still end up with the following error in my console:

Uncaught (in promise) Error: [object Response]

Should the .catch(...) block not be handling this? What am I missing? Should I be getting this anyway? If so, why?

Secondly, I've read that wrapping the fetch inside a new Promise is an anti-pattern, and there was an almost-hint that this may be what's causing problems here. All the examples I've e across use it in this fashion. What's the alternative? How do I fire the resolve/reject to dispatch the next actions without the wrapper?

Any help will be greatly appreciated. Thanks masters of the web.

-------------EDIT 1----------------

From the official redux-promise-middleware github examples, they have the following code:

export default function request(url, options) {
  return new Promise((resolve, reject) => {
    if (!url) reject(new Error('URL parameter required'));
    if (!options) reject(new Error('Options parameter required'));

    fetch(url, options)
      .then(response => response.json())
      .then(response => {
        if (response.errors) reject(response.errors);
        else resolve(response);
      })
      .catch(reject);
  });
}

It seems to intention with the middleware is to wrap fetch inside a new Promise and catching any rejects. If anyone has a working alternative way of implementing this using redux-promise-middleware, or can elaborate on why its following this pattern that would be greatly appreciated.

-------------EDIT 2----------------

Not sure what the intended way of implementing this is or how to avoid the Uncaught error in the promise. Simply calling Promise.reject(...) results in an uncaught error unless you include error handling functions: Promise.reject(...).then(() =>{...}, error => {...}). Including this with the middleware results in the rejected action never being dispatched. I've moved away from redux-promise-middleware till I can find a suitable fix and/or implementation.

I'm learning React, along with pretty much all the necessary technology around it all at once - so I often get tripped up by things I should probably know already.

I've encountered a problem when it es to error handling my async events. I've scoured the web and nothing really answers exactly what I'm looking for.

I'm currently using redux with redux-promise-middleware to handle the async actions, like this:

export function myFunc() {
  return {
    type: FETCH_FUNC,
    payload: new Promise((resolve, reject) => {
      fetch ('some/url/location/from/which/to/fetch')
        .then( response => {
          if (!response.ok){
            throw new Error(response);
            }
          resolve(response.json());
        }).catch(error => {
          reject(error);
        }),
    })
  };
}

There are two things here: first, the code works just fine when no errors are present. However, when I purposely create an error in the code the correct methods are firing but I still end up with the following error in my console:

Uncaught (in promise) Error: [object Response]

Should the .catch(...) block not be handling this? What am I missing? Should I be getting this anyway? If so, why?

Secondly, I've read that wrapping the fetch inside a new Promise is an anti-pattern, and there was an almost-hint that this may be what's causing problems here. All the examples I've e across use it in this fashion. What's the alternative? How do I fire the resolve/reject to dispatch the next actions without the wrapper?

Any help will be greatly appreciated. Thanks masters of the web.

-------------EDIT 1----------------

From the official redux-promise-middleware github examples, they have the following code:

export default function request(url, options) {
  return new Promise((resolve, reject) => {
    if (!url) reject(new Error('URL parameter required'));
    if (!options) reject(new Error('Options parameter required'));

    fetch(url, options)
      .then(response => response.json())
      .then(response => {
        if (response.errors) reject(response.errors);
        else resolve(response);
      })
      .catch(reject);
  });
}

It seems to intention with the middleware is to wrap fetch inside a new Promise and catching any rejects. If anyone has a working alternative way of implementing this using redux-promise-middleware, or can elaborate on why its following this pattern that would be greatly appreciated.

-------------EDIT 2----------------

Not sure what the intended way of implementing this is or how to avoid the Uncaught error in the promise. Simply calling Promise.reject(...) results in an uncaught error unless you include error handling functions: Promise.reject(...).then(() =>{...}, error => {...}). Including this with the middleware results in the rejected action never being dispatched. I've moved away from redux-promise-middleware till I can find a suitable fix and/or implementation.

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Feb 28, 2017 at 16:37 ndvndv 331 silver badge4 bronze badges 5
  • Yes, you should not wrap the fetch inside a new promise because actually returning the fetch does the exact thing you do with the Promise. Now you just add a new level of indirection. – caisah Commented Feb 28, 2017 at 16:48
  • @caisah Thanks for the response. This was what I thought. In order to trigger to follow up actions, of either Fulfilled or Rejected, from the middleware, I need to fire off the resolve()/reject() functions. How do I do this without wrapping the fetch in the Promise? – ndv Commented Feb 28, 2017 at 16:55
  • I think something like: payload: fetch('url').then((response) => { ... return response.json() }), – caisah Commented Feb 28, 2017 at 17:03
  • Thanks again, this has sorted out the problem of doing away with the wrapper and getting resolve() to fire correctly. However, I'm still unclear on how to get reject() to happen. I've included, as above, a check to see if the response is 'ok', if not I'm throwing an error, which is successfully entering the catch block. However, all responses are now being seen by the middleware as successful - any suggestions on how to handle the error case? – ndv Commented Feb 28, 2017 at 17:31
  • 1 perhaps on error, dispatch an error action such as dispatch(throwError(error)) – Sean Kwon Commented Feb 28, 2017 at 17:34
Add a ment  | 

3 Answers 3

Reset to default 3

I guess what you are getting is the expected result and this is mentioned clearly in the middleware documentation:

The middleware dispatches rejected actions but does not catch rejected promises. As a result, you may get an "uncaught" warning in the console. This is expected behavior for an uncaught rejected promise. It is your responsibility to catch the errors and not the responsibility of redux-promise-middleware.

But if you ask about best practices this is what i ended up doing from long time ago and it's working perfectly with me:

1- For some promises you can do as mentioned in the documentation:

dispatch({
    type: 'FOO_ACTION',
    payload: new Promise(() => {
      throw new Error('foo');
    })
  }).catch(error => {
    // catch and handle error or do nothing
  });

2- To catch all rejected promises globally add this middleware before the redux-promise-middleware as follow:

/**
 * a utility to check if a value is a Promise or not
 * @param value
 */
const isPromise = value => value !== null && typeof value === 'object' && typeof value.then === 'function';


export default () => {

  const middleWares = [];

  // global error middleware
  middleWares.push(() => next => action => {

    // If not a promise, continue on
    if (!isPromise(action.payload)) {
      return next(action);
    }

    /**
     * include a property in `meta and evaluate that property to check if this error will be handled locally
     *
     * if (!action.meta.localError) {
     *   // handle error
     * }
     *
     * The error middleware serves to dispatch the initial pending promise to
     * the promise middleware, but adds a `catch`.
     */
    if (!action.meta || !action.meta.localError) {
      // Dispatch initial pending promise, but catch any errors
      return next(action).catch(error => {
        if (config.showErrors) { // here you can decide to show or hide errors
          console.log(`${action.type} unhandled rejection caught at middleware with reason: ${JSON.stringify(error.message)}.`);
        }
        return error;
      });
    }

    return next(action);
  });

  // middleware
  middleWares.push(thunk);
  middleWares.push(promise());  
  middleWares.push(logger());

  return applyMiddleware(...middleWares);
}

i guess this is exactly what you are looking for ;)

Extra I highly remend axios over fetch for the following reasons:

  • the axios module automatically reject the promise if the request has an error code which is something you need to keep manually handle in fetch
  • in axios you can create instance with default base-url,header,interceptors ...
  • in axios you can cancel any previous request using a token this is extremely useful specially for autoplete and chat applications
  • also axios internally automatically switch between xhr and http modules to perform the ajax request based on the environment (NodeJs or Browser), i personally used the same redux actions in electron, nodejs, browser and react-native and it's all working fine

Following up on caisah 's ment, get rid of the indirection. You can resolve or reject a promise by simply resolving or rejecting with a new promise object

export function myFunc() {
  return {
    type: FETCH_FUNC,
    payload: fetch ('some/url/location/from/which/to/fetch')
        .then(response => {
          if (!response.ok){
            throw new Error(response);
          }
          return Promise.resolve(response.json());
        }).catch(error => {
          return Promise.reject(error)
        }),
    })
  };
}

myFunc().payload.then(json => /* do stuff with json*/)

P.S the returns may be redundant.

I’ve used "Catching Errors Globally" presented in "Catching Errors Thrown by Rejected Promises", as shown, when calling applyMiddleware the errorMiddleware should be before the promiseMiddleware. To filter the action types where to apply this middleware i've preferred a regex:

This is the store creation:

import { createStore, bineReducers, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import errorMiddleware from './errorMiddleware';

import adultosReducer from './adultosReducer';

const rootReducer = bineReducers({
  adultosReducer
});

const poseStoreWithMiddleware = applyMiddleware(errorMiddleware, promiseMiddleware())(
  createStore
);

export default poseStoreWithMiddleware(rootReducer);

This is the error middleware:

import isPromise from 'is-promise';
import _ from 'lodash';

const tiposAction = /^ADULTO/i;

export default function errorMiddleware() {
  return next => action => {
    // If not a promise, continue on
    if (!isPromise(action.payload)) {
      return next(action);
    }

    console.log('errorMiddleware: action.type', action.type);
    if (action.type.match(tiposAction)) {
      // Dispatch initial pending promise, but catch any errors
      return next(action).catch(error => {
        console.log('catching action', error);

        return error;
      });
    }

    return next(action);
  };
}

That way you show gently to the user the error because the rejected action is dispatched without the Unhandled promise. And of course there is no need to add redux-thunk.

发布评论

评论列表(0)

  1. 暂无评论