web-dev-qa-db-ja.com

redux-thunkとディスパッチ結果内の例外の処理

私はReduxのドキュメントから適応した基本的なサンクアクションクリエーターとレデューサーを持っています: http://redux.js.org/docs/advanced/AsyncActions.html

_// action creator

function fetchPosts () {
  return dispatch => {
    dispatch({ type: 'FETCH_POSTS_REQUEST' })

    return fetch('http://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(json => dispatch({ type: 'FETCH_POSTS_SUCCESS', items: json }))
      // THIS CATCHES FETCH REQUEST ERRORS, AND COMPONENT LEVEL ERRORS
      .catch(error => dispatch({ type: 'FETCH_POSTS_FAILURE', error: error.message }))
  }
}

// reducer

function reducer (state = { isFetching: false, items: [] }, action) {
  switch (action.type) {
    case 'FETCH_POSTS_REQUEST':
      return Object.assign({}, state, { isFetching: true })
    case 'FETCH_POSTS_SUCCESS':
      return Object.assign({}, state, { isFetching: false, items: action.items })
    case 'FETCH_POSTS_FAILURE':
      return Object.assign({}, state, { isFetching: false })
    default:
      return state
  }
}
_

小道具として状態が渡されるReactコンポーネントで、投稿アイテムの存在を確認し、存在する場合はコンポーネントレベルのエラーを強制します。

_const Test = props => {
  if (!props.items.length) return null
  throw new Error('Error!')
}
_

アプリを起動するとき:

  1. fetchPostsアクションクリエーターが呼び出されます
  2. HTTP要求が行われ、応答後にFETCH_POSTS_SUCCESSアクションがディスパッチされます。
  3. コンポーネントは、状態の結果のitemsで更新され、_props.invalidProperty.error_の読み取りを試みます。
  4. これによりJS例外が発生します:_Cannot read property 'error' of undefined_

ここまでは順調ですね。

問題は、コンポーネントからのJS例外がコンソールに出力されないことです。代わりに、フェッチプロミスのcatch()ブロックがエラーをキャッチし、FETCH_POSTS_FAILUREアクションをディスパッチします。

これには、ストアの更新によって影響を受けたコンポーネントのすべてのエラーを飲み込む効果があります。 FETCH_POSTS_FAILURE状態変更がディスパッチされますが、これは正しくないと感じます-実際に投稿をフェッチする際のエラーはありませんでしたが、それらの投稿を使用するコンポーネントのダウンストリームでエラーが発生しました。

非同期リクエストのエラーを、ディスパッチを介して状態を変更した結果として発生する他のランダムエラーから分離するのに役立つパターンを探しています。


編集:

Redux githubリポジトリの非同期の例の例: https://github.com/nandastone/redux/commit/88ab48040ce41c39d8daba8cc0c13a6f32c38adf#diff-eeb827d44ad03655e63b7e9319a03dd4R6

8
Matt Stone

_Promise.catch_ハンドラーは、解決ハンドラーまたは拒否ハンドラーからスローされたエラーもキャッチします。

_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
});
_

fetchからのエラーのみを処理し、解決ハンドラーでdispatch({ type: 'FETCH_POSTS_SUCCESS', items: json })の呼び出しによってスローされたエラーが、catchハンドラーでキャッチされないようにするには、拒否を添付します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はステータスコード> = 400をエラーとして処理しないため、上記の呼び出しはネットワークエラーまたはCORSエラーがある場合にのみ拒否されます。そのため、解決ハンドラでステータスコードを確認する必要があります。

_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 });
});
_

Reactコンポーネントでスローされたエラーは、Reactが矛盾した状態のままになる可能性があるため、後続のrendersを防ぎ、アプリケーションが応答しなくなることに注意してください。 UIイベント React Fiber は、エラーの境界でこの問題に対処します。

7
c.P.u1

エラーハンドラーを前のthenブロックに移動することを検討できます。

私は原理の簡単なデモンストレーションを書きました: 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") })
0
OlliM

これは、私が作成したフェッチラッパーからのコードのブロックです。 checkStatusプロミスチェーンにexecuteRequestが表示されます。ここで、_response.ok_を使用して2xx以外の応答がないかチェックしています。私のAPIエラーはJSONを返すため、2xx以外の応答をparseResponseに渡し、次にreject()に解析されたエラーデータを拒否してエラーとして返します。 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);
_
0
adrice727