admin管理员组

文章数量:1323529

Background of the Problem:

I am building a React/Redux app that uses redux-thunk and wretch (a fetch wrapper) to handle asynchronous requests.

I have a few search actions that can vary significantly in their load times, causing undesirable behavior.

I have looked into using AbortController(), but it's either cancelling all my requests outright, or failing to cancel the previous request.

example problem:

  • Request a search for "JOHN", then request a search for "JOHNSON".
  • Results for "JOHNSON" return first, and then results for "JOHN" return later and overwrite the "JOHNSON" results.

Goal:

Initiating a request should abort previous pending requests.

example desired behavior:

  • Request a search for "JOHN", then request a search for "JOHNSON".
  • Upon initiating the request for "JOHNSON", the pending request for "JOHN" is aborted.

Code:

actions.js

The fetchData action gets called via an onClick or by other functions.

import api from '../../utils/request';
export function fetchData(params) {
  return dispatch => {
    dispatch(requestData());
    return api
      .query(params)
      .url('api/data')
      .get()
      .fetchError(err => {
        console.log(err);
        dispatch(apiFail(err.toString()));
      })
      .json(response => dispatch(receiveData(response.items, response.totalItems)))
  }
}

export function requestData() {
  return {
    type: REQUEST_DATA,
    waiting: true,
  }
}

export function receiveData(items, totalItems) {
  return {
    type: RECEIVE_DATA,
    result: items,
    totalItems: totalItems,
    waiting: false,
  }
}

export function apiFail(err) {
  return {
    type: API_FAIL,
    error: err,
    waiting: false,
  }
}

utils/request.js

This is wretch import. Wretch is a fetch wrapper so it should function similarly to fetch.

import wretch from 'wretch';

/**
 * Handles Server Error
 * 
 * @param {object}      err HTTP Error
 * 
 * @return {undefined}  Returns undefined
 */
function handleServerError(err) {
  console.error(err);
}

const api = wretch()
  .options({ credentials: 'include', mode: 'cors' })
  .url(window.appBaseUrl || process.env.REACT_APP_API_HOST_NAME)
  .resolve(_ => _.error(handleServerError))

export default api;

Attempt:

I've tried using wretch's .signal() parameter with an AbortController(), calling .abort() after the request, but that aborts all requests, causing my app to break. Example below:

import wretch from 'wretch';

/**
 * Handles Server Error
 * 
 * @param {object}      err HTTP Error
 * 
 * @return {undefined}  Returns undefined
 */
function handleServerError(err) {
  console.error(err);
}
const controller = new AbortController();

const api = wretch()
  .signal(controller)
  .options({ credentials: 'include', mode: 'cors' })
  .url(window.appBaseUrl || process.env.REACT_APP_API_HOST_NAME)
  .resolve(_ => _.error(handleServerError))

controller.abort();
export default api;

I've tried moving the logic around to various places, but it seems abort all actions or abort none of them.

Any advice as to how to go about this would be appreciated, this is critical for my team.

Thank you

Background of the Problem:

I am building a React/Redux app that uses redux-thunk and wretch (a fetch wrapper) to handle asynchronous requests.

I have a few search actions that can vary significantly in their load times, causing undesirable behavior.

I have looked into using AbortController(), but it's either cancelling all my requests outright, or failing to cancel the previous request.

example problem:

  • Request a search for "JOHN", then request a search for "JOHNSON".
  • Results for "JOHNSON" return first, and then results for "JOHN" return later and overwrite the "JOHNSON" results.

Goal:

Initiating a request should abort previous pending requests.

example desired behavior:

  • Request a search for "JOHN", then request a search for "JOHNSON".
  • Upon initiating the request for "JOHNSON", the pending request for "JOHN" is aborted.

Code:

actions.js

The fetchData action gets called via an onClick or by other functions.

import api from '../../utils/request';
export function fetchData(params) {
  return dispatch => {
    dispatch(requestData());
    return api
      .query(params)
      .url('api/data')
      .get()
      .fetchError(err => {
        console.log(err);
        dispatch(apiFail(err.toString()));
      })
      .json(response => dispatch(receiveData(response.items, response.totalItems)))
  }
}

export function requestData() {
  return {
    type: REQUEST_DATA,
    waiting: true,
  }
}

export function receiveData(items, totalItems) {
  return {
    type: RECEIVE_DATA,
    result: items,
    totalItems: totalItems,
    waiting: false,
  }
}

export function apiFail(err) {
  return {
    type: API_FAIL,
    error: err,
    waiting: false,
  }
}

utils/request.js

This is wretch import. Wretch is a fetch wrapper so it should function similarly to fetch.

import wretch from 'wretch';

/**
 * Handles Server Error
 * 
 * @param {object}      err HTTP Error
 * 
 * @return {undefined}  Returns undefined
 */
function handleServerError(err) {
  console.error(err);
}

const api = wretch()
  .options({ credentials: 'include', mode: 'cors' })
  .url(window.appBaseUrl || process.env.REACT_APP_API_HOST_NAME)
  .resolve(_ => _.error(handleServerError))

export default api;

Attempt:

I've tried using wretch's .signal() parameter with an AbortController(), calling .abort() after the request, but that aborts all requests, causing my app to break. Example below:

import wretch from 'wretch';

/**
 * Handles Server Error
 * 
 * @param {object}      err HTTP Error
 * 
 * @return {undefined}  Returns undefined
 */
function handleServerError(err) {
  console.error(err);
}
const controller = new AbortController();

const api = wretch()
  .signal(controller)
  .options({ credentials: 'include', mode: 'cors' })
  .url(window.appBaseUrl || process.env.REACT_APP_API_HOST_NAME)
  .resolve(_ => _.error(handleServerError))

controller.abort();
export default api;

I've tried moving the logic around to various places, but it seems abort all actions or abort none of them.

Any advice as to how to go about this would be appreciated, this is critical for my team.

Thank you

Share Improve this question asked Feb 20, 2020 at 20:13 PepPep 3872 gold badges8 silver badges17 bronze badges 2
  • This is something that sagas do really well: redux-saga.js – Ed Lucas Commented Feb 21, 2020 at 3:43
  • I appreciate the input, but I fear that switching over to redux-saga will be too much of a rebuild given our limited time and resources. – Pep Commented Feb 21, 2020 at 15:44
Add a ment  | 

1 Answer 1

Reset to default 5

I feel pretty silly right now, but this is what it took to get it working.

Solution Steps:

  • Set an AbortController to the initialState of the reducer

reducer.js

export default (state = {
  controller: new AbortController(),
}, action) => {
  switch (action.type) {
    ...
  • Get the AbortController from the state, at the beginning of the fetch action and abort it.
  • Create a new AbortController and pass it into the requestData action.
  • Pass the new AbortController into the signal() param of the wretch call.

actions.js

export function fetchData(params) {
  return (dispatch, getState) => {
    const { controller } = getState().reducer;
    controller.abort();

    const newController = new AbortController();
    dispatch(requestData(newController));
    return api
      .signal(newController)
      .query(params)
      .url('api/data')
      .get()
      .fetchError(err => {
        console.log(err);
        dispatch(apiFail(err.toString()));
      })
      .json(response => dispatch(receiveData(response.items, response.totalItems)))
  }
}

export function requestData(controller) {
  return {
    type: REQUEST_DATA,
    waiting: true,
    controller,
  }
}

In the reducer, for the case of the requestData action, set the new AbortController to the state.

reducer.js

case REQUEST_DATA:
  return {
    ...state,
    waiting: action.waiting,
    controller: action.controller
  };

There's some additional functionality with wretch, an .onAbort() param, that allows you to dispatch other actions when the request is aborted. I haven't coded that out yet, but I figured I'd include the info for anyone else struggling with this.

本文标签: javascriptCancel previous fetch request with reduxthunkStack Overflow