1

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 comes 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 come 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.

Community
  • 1
  • 1
ndv
  • 23
  • 1
  • 4
  • 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 Feb 28 '17 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 Feb 28 '17 at 16:55
  • I think something like: `payload: fetch('url').then((response) => { ... return response.json() }),` – caisah Feb 28 '17 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 Feb 28 '17 at 17:31
  • 1
    perhaps on error, dispatch an error action such as `dispatch(throwError(error))` – Sean Kwon Feb 28 '17 at 17:34

3 Answers3

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 recommend 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 autocomplete 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
Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76
  • Thanks fareed, this was very insightful. – ndv May 25 '17 at 06:19
  • I am new to Redux and to Redux-promise. The config.showErrors part what is that? Are you updating a state is there a component in the layout which updates on changes error states etc..? That would be much appreciated thanks. – eGlu Sep 24 '17 at 16:42
  • config.showErrors is just an example of when to show the error in the console you can use any variable – Fareed Alnamrouti Sep 24 '17 at 17:03
  • @fareednamrouti I understand that. I am trying at the moment to call an action from my action creator, updateError() where I pass in the error message. I am doing this where the if (config.showerror) block is written above. It works, wondering if thats the way to do it? Also I keep getting xhr.js:175 POST http://localhost:3114/api/users/authenticate 403 (Forbidden) javascript error in console. Not sure why that is not being caught. – eGlu Sep 24 '17 at 18:38
1

Following up on caisah 's comment, 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.

Sean Kwon
  • 907
  • 6
  • 12
  • Thank you kindly @Sean Kwon. We're very nearly there. All of this works as intended, however I'm still ending up with `Uncaught (in promise) Error!` in the console. Starting to seem like maybe the Error within the .catch(...) block needs to be caught elsewhere as well? – ndv Feb 28 '17 at 17:48
  • Removing the error from the then chain results in all responses being considered a success again. – ndv Feb 28 '17 at 17:58
  • You may be right. Did you try adding a catch thread to the outer function that calls your myFunc function? – Sean Kwon Feb 28 '17 at 18:05
  • I'll investigate further and see if I can find anything that works and report back. From the official `redux-promise-middleware` github example they seem to be wrapping fetch in a new Promise - might have to find an alternative for handling promises, it doesn't seem like rocket science to just code your own middleware to handle this. – ndv Feb 28 '17 at 18:19
  • off the topic, in your example the catch method and the Promise.resolve in the return are completely useless – Fareed Alnamrouti May 23 '17 at 15:57
0

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, combineReducers, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import errorMiddleware from './errorMiddleware';

import adultosReducer from './adultosReducer';

const rootReducer = combineReducers({
  adultosReducer
});

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

export default composeStoreWithMiddleware(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.

Hans Poo
  • 804
  • 1
  • 8
  • 17