redux-thunk and handling exceptions inside dispatch results redux-thunk and handling exceptions inside dispatch results reactjs reactjs

redux-thunk and handling exceptions inside dispatch results


A Promise.catch handler also catches any errors thrown from the resolution or rejection handler.

fetch('http://jsonplaceholder.typicode.com/posts').then(res => {  throw new Error();}).catch(err => {  //will handle errors from both the fetch call and the error from the resolution handler});

To handle only the errors from fetch and ensure that any error thrown by the call to dispatch({ type: 'FETCH_POSTS_SUCCESS', items: json }) in the resolution handler isn't caught in the catch handler, attach a rejection handler to fetch.

return fetch('http://jsonplaceholder.typicode.com/posts').then(response => response.json, error => {    dispatch({ type: 'FETCH_POSTS_FAILURE', error: error.message });}).then(json => dispatch({ type: 'FETCH_POSTS_SUCCESS', items: json }), error => {    //response body couldn't be parsed as JSON});

fetch doesn't treat status codes >= 400 as errors, so the above call would only be rejected if there's a network or CORS error, which is why the status code must be checked in the resolution handler.

function fetchHandler(res) {  if (res.status >= 400 && res.status < 600) {    return Promise.reject(res);  }  return res.json();}return fetch('http://jsonplaceholder.typicode.com/posts').then(fetchHandler, error => {    //network error    dispatch({ type: 'NETWORK_FAILURE', error });}).then(json => dispatch({ type: 'FETCH_POSTS_SUCCESS', items: json }), error => {    dispatch({ type: 'FETCH_POSTS_FAILURE', error: error.message });});

Please note that any errors thrown in React components may leave React in an inconsistent state, thereby preventing subsequent renders and making the application unresponsive to UI events. React Fiber addresses this issue with error boundaries.


You could consider moving your error handler into the previous then block.

I wrote a simple demonstration of the principle: https://codepen.io/anon/pen/gWzOVX?editors=0011

const fetch = () => new Promise((resolve) => {  setTimeout(resolve, 100);});const fetchError = () => new Promise((resolve, reject) => {  setTimeout(reject, 200)});fetch()  .then(() => { throw new Error("error") })  .catch(() => { console.log("error in handler caught") })fetch()  .then(() => { throw new Error("error") },         () => { console.log("error in handler not caught") })fetchError()  .then(() => { throw new Error("error") })  .catch(() => { console.log("error in fetch caught 1") })fetchError()  .then(() => { throw new Error("error") },         () => { console.log("error in fetch caught 2") })


This is a block of code from a fetch wrapper that I wrote. You'll see checkStatus in the executeRequest promise chain, which is where I'm checking for any non-2xx responses using response.ok. Since my API errors return JSON, I pass any non-2xx responses to parseResponse and then reject() the parsed error data which is in turn rejected and returned as an error by executeRequest:

 /**   * Parse a reponse based on the type   * @param {Response} response   * @returns {Promise} <resolve: *, reject: Error>   */  const parseResponse = (response) => {    const contentType = (response.headers.get('content-type') || '').split(';')[0];    if (contentType === 'application/json') {      return response.json();    } else if (contentType === 'multipart/form-data') {      return response.formData();    } else if (contentType === 'text/html') {      return response.text();    } else if (contentType === 'application/octet-stream') {      return response.blob();    }  };  /**   * Check for API-level errors   * @param {Response} response   * @returns {Promise} <resolve: Response, reject: Error>   */  const checkStatus = (response) =>    new Promise((resolve, reject) => {      if (response.ok) {        return resolve(response);      }      parseResponse(response)        .then(reject)        .catch(reject);    });  /**   * Create a new Request object   * @param {String} method   * @param {String} route   * @param {*} [data]   * @param {Object} [options]   * @returns {Request}   */  const buildRequest = (method, route, data = null, definedOptions = {}) => {    const options = Object.assign({}, defaultOptions, validateOptions(definedOptions));    const body = () => data ? { body: options.json ? JSON.stringify(data) : data } : {};    const baseOptions = {      method: method.toUpperCase(),      mode: options.mode,      headers: new Headers(headers(options.headers)),    };    const requestOptions = Object.assign({}, baseOptions, body());    return new Request(getURL(route), requestOptions);  };  /**   * Execute a request using fetch   * @param {String} method   * @param {String} route   * @param {*} [body]   * @param {Object} [options]   */  const executeRequest = (method, route, body, options) =>    new Promise((resolve, reject) => {      fetch(buildRequest(method, route, body, options))        .then(checkStatus)        .then(parseResponse)        .then(resolve)        .catch(reject);