如何在Redux中执行HTTP请求的异常处理

问题描述 投票:0回答:1

我正在学习对redux和redux-observable的反应。特别是,我试图了解如何进行错误处理。到目前为止:

  1. 我设法创建了一个可观察到的Redux的史诗,该史诗可响应提取操作。具体来说,它发出一个ajax请求,并以成功或失败操作作为响应。

    a。如果ajax请求成功,那么该事件将以响应数据作为有效负载触发成功动作。

    b。如果请求失败,则该史诗会触发失败操作,并以错误作为有效负载。

  2. 该组件检查是否存在错误状态,如果存在,则将其抛出到周围的错误边界组件。

我使用此方法遇到的问题是,错误仍保留在存储状态中。例如,假设以下本地开发环境:

  1. React / Redux客户端使用Redux可观察的史诗来发出http请求。
  2. Rest API服务器(未启动

当我的React / Redux客户端向Rest API服务器发出请求时,会引发网络连接错误,触发故障操作并导致错误状态被存储。然后,我START Rest API服务器,并尝试从React / Redux客户端发出相同的http请求。来自上一个请求的错误仍保留在存储中。因此,将显示错误通知。

随后,如何重置以允许无错误的全新请求?使用中间件(例如redux-observable或redux-saga)时,是否有任何建议的模式用于使用react和redux进行错误处理?

Epic

import { Epic } from 'redux-observable';
import { isActionOf } from 'typesafe-actions';
import { of } from 'rxjs';
import { catchError, filter, map, switchMap } from 'rxjs/operators';

import { fetchCoursesAsync } from './actions';
import { RootAction, RootState, Services } from 'ReduxTypes';

export const fetchCoursesRequestAction: Epic<
  RootAction,
  RootAction,
  RootState,
  Services
> = (action$, state$, { courseServices }) =>
  action$.pipe(
    filter(isActionOf(fetchCoursesAsync.request)),
    switchMap(() =>
      courseServices.default.getCourses().pipe(
        map(fetchCoursesAsync.success),
        catchError((error: Error) =>
          of(fetchCoursesAsync.failure({ hasError: true, error: error })),
        ),
      ),
    ),
  );

功能组件

import React, { useEffect } from 'react';

import { connect } from 'react-redux';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';

import Course from '../components/Course/Course';
import { courseModels } from '../redux/features/course';
import { courseSelectors } from '../redux/features/course';
import { fetchCoursesAsync } from '../redux/features/course/actions';
import { RootState } from 'ReduxTypes';

type ErrorReport = { hasError: boolean; error?: Error };
type StateProps = {
  isLoading: boolean;
  courses: courseModels.Course[];
  error: ErrorReport;
};

/**
 * Redix state and dispatch mappings
 */
const dispatchProps = {
  fetchCourses: fetchCoursesAsync.request,
};

const mapStateToProps = (state: RootState): StateProps => ({
  isLoading: state.courses.isLoadingCourses,
  courses: courseSelectors.getReduxCourses(state.courses),
  error: courseSelectors.getReduxCoursesError(state.courses),
});

/**
 * Component property type definitions
 */
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;

/**
 * CourseList component
 */
const CourseList = ({
  courses = [],
  error,
  fetchCourses,
  isLoading,
}: Props): JSX.Element => {
  // fetch course action on mount
  useEffect(() => {
    fetchCourses();
  }, []);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error && error.hasError && error.error) {
    throw error.error; // notify surrounding Error Boundary 
  }
  return (
    <div style={{ marginTop: 20, padding: 30 }}>
      {
        <Grid container spacing={2 as GridSpacing} justify="center">
          {courses.map(element => (
            <Grid item key={element.courseID}>
              <Course course={element} />
            </Grid>
          ))}
        </Grid>
      }
    </div>
  );
};

/**
 * Exports
 */
export default connect(
  mapStateToProps,
  dispatchProps,
)(CourseList);

reactjs typescript redux error-handling redux-observable
1个回答
0
投票

认为通过提出新的获取请求或获取成功时,清除与获取相关的错误状态,已经解决了根本问题。我在下面包括了reducer代码和客户端组件代码。接受其他建议。...

如果客户端组件检测到错误状态,则将其呈现。到目前为止,我发现,如果客户端组件抛出错误(由错误状态封装),则会被周围的错误边界捕获。但是,下次访问该组件时,在useEffect函数中似乎未引发提取动作。我将不得不对此进行进一步调查,以确定问题是否在我的ErrorBoundary组件内。...也许是随后的单独问题。

减速器

import { combineReducers } from 'redux';
import { createReducer } from 'typesafe-actions';

import { fetchCoursesAsync } from './actions';

import { Course } from './model';

/**
 * Error State
 */
type ErrorReport = Readonly<{
  hasError: boolean;
  error: undefined;
}>;
const initialErrorState: ErrorReport = {
  hasError: false,
  error: undefined,
};

const isLoadingCourses = createReducer(false as boolean)
  .handleAction([fetchCoursesAsync.request], () => true)
  .handleAction(
    [fetchCoursesAsync.success, fetchCoursesAsync.failure],
    () => false,
  );

const courses = createReducer([] as Course[]).handleAction(
  fetchCoursesAsync.success,
  (state, action) => action.payload,
);

const error = createReducer(initialErrorState)
  .handleAction(
    [fetchCoursesAsync.request, fetchCoursesAsync.success],
    (state): ErrorReport => ({
      hasError: false,
      error: undefined,
    }),
  )
  .handleAction(
    [fetchCoursesAsync.failure],
    (state, action): ErrorReport => ({
      hasError: action.payload.hasError,
      error: action.payload.error,
    }),
  );

const coursesReducer = combineReducers({
  isLoadingCourses,
  courses,
  error,
});

export default coursesReducer;
export type CoursesState = ReturnType<typeof coursesReducer>;

CourseList

import React, { useEffect } from 'react';

import { connect } from 'react-redux';
import Grid from '@material-ui/core/Grid';
import { GridSpacing } from '@material-ui/core/Grid';

import Course from '../components/Course/Course';

import { courseModels } from '../redux/features/course';
import { courseSelectors } from '../redux/features/course';
import { fetchCoursesAsync } from '../redux/features/course/actions';
import { RootState } from 'ReduxTypes';

type ErrorReport = { hasError: boolean; error?: Error };
type StateProps = {
  isLoading: boolean;
  courses: courseModels.Course[];
  error: ErrorReport;
};

/**
 * Redux dispatch and state mappings
 */
const dispatchProps = {
  fetchCourses: fetchCoursesAsync.request,
};

const mapStateToProps = (state: RootState): StateProps => ({
  isLoading: state.courses.isLoadingCourses,
  courses: courseSelectors.getReduxCourses(state.courses),
  error: courseSelectors.getReduxCoursesError(state.courses),
});

/**
 * Component property type definitions
 */
type Props = ReturnType<typeof mapStateToProps> & typeof dispatchProps;

/**
 * CourseList component
 */
const CourseList = ({
  courses = [],
  error,
  fetchCourses,
  isLoading,
}: Props): JSX.Element => {
  // fetch course action on mount
  useEffect(() => {
    console.log('COURSELIST FETCHING COURSES');
    fetchCourses();
  }, [fetchCourses]);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error && error.hasError && error.error) {
    // throw error.error;
    // if throw the error from state then encapsulating error boundary catches and displays
    // however subsequent request actions are not processed by redux-observable action stream
    // needs further investigation and if necessary a subsequent question posted....

    // if render error inside component then redux-observable stream continues
    // to process subsequent actions.
    return <p>{JSON.stringify(error.error, null, 2)}</p>;
  }

  return (
    <div style={{ marginTop: 20, padding: 30 }}>
      {
        <Grid container spacing={2 as GridSpacing} justify="center">
          {courses.map(element => (
            <Grid item key={element.courseID}>
              <Course course={element} />
            </Grid>
          ))}
        </Grid>
      }
    </div>
  );
};

/**
 * Exports
 */
export default connect(
  mapStateToProps,
  dispatchProps,
)(CourseList);

© www.soinside.com 2019 - 2024. All rights reserved.