You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Redux-Thunk 并发请求:两个Action逻辑实现相关技术咨询

Great question! Let's walk through your current Redux-Thunk action logic, evaluate how it holds up in concurrent request scenarios, and then dive into actionable optimizations.

Evaluating Your Current Redux-Thunk Logic for Concurrency

First, let's break down what's working and what could be improved in your existing code:

What's Reasonable About the Current Approach

  • Duplicate Request Prevention: Your getChats action checks isLoading before firing a new request, which is a solid first line of defense against concurrent duplicate requests. This prevents unnecessary network calls when a request is already in flight—great for reducing overhead.
  • Dependency Handling: Having getChat wait for getChats to complete makes sense if individual chat data relies on the list being loaded first; this ensures you don't try to fetch a chat without knowing it exists (assuming your API requires that context).

Key Issues in Concurrent Scenarios

  1. Permanent Cache Block: The isRequested flag is a one-time toggle. Once the initial getChats request completes, any subsequent calls to getChats will immediately resolve without re-fetching data. This breaks use cases like pull-to-refresh or when backend data updates.
  2. Missing Error Handling: There's no catch block for failed fetch calls in getChats, and getChat doesn't handle cases where getChats fails. In concurrent scenarios, this can leave your app in a broken state with no feedback.
  3. No Per-Chat Request Guard: getChat doesn't check if a specific chat is already loading, so multiple concurrent calls for the same chat ID will fire duplicate requests.
  4. Uncancelled Requests: If a component unmounts or the user navigates away mid-request, the ongoing fetch will still complete and dispatch actions, leading to race conditions (e.g., updating state for a component that no longer exists).

Optimization Solutions

Let's fix these issues with targeted improvements:

1. Flexible Request Caching & Re-Fetching

Replace the static isRequested flag with a timestamp-based cache system, and add a refetch parameter to allow forced updates:

getChats({ refetch = false } = {}) {
  return (dispatch, getState) => {
    const state = getState().chats;
    // Prevent duplicate in-flight requests unless refetching
    if (state.isLoading && !refetch) {
      console.log('CHATS_REQUESTING_BUSY');
      return Promise.resolve(state.list);
    }

    // Allow re-fetching after a 5-minute cache window (adjust as needed)
    const shouldFetch = refetch || !state.lastFetched || Date.now() - state.lastFetched > 5 * 60 * 1000;
    if (!shouldFetch) {
      console.log('CHATS_CACHED');
      return Promise.resolve(state.list);
    }

    console.log('CHATS_REQUESTING');
    const abortController = new AbortController();
    dispatch({ type: 'CHATS_LOADING', payload: { abortController } });

    return fetch('/api/chats', { signal: abortController.signal })
      .then(response => {
        if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
        return response.json();
      })
      .then(data => {
        console.log('CHATS_REQUESTED');
        dispatch({
          type: 'CHATS_LOADED',
          payload: { list: data, lastFetched: Date.now(), abortController: null }
        });
        return data;
      })
      .catch(error => {
        if (error.name !== 'AbortError') {
          console.error('CHATS_REQUEST_FAILED', error);
          dispatch({ type: 'CHATS_FAILED', payload: { error, abortController: null } });
        } else {
          console.log('CHATS_REQUEST_ABORTED');
          dispatch({ type: 'CHATS_IDLE' });
        }
        throw error; // Propagate error for upstream handling
      });
  };
}

2. Per-Chat Request Guards & Error Handling

Update getChat to prevent duplicate requests for the same ID, and handle errors from both getChats and the individual chat fetch:

getChat(id) {
  return (dispatch, getState) => {
    const state = getState().chats;

    // Prevent duplicate in-flight requests for this chat
    if (state.chatLoadings?.[id]) {
      console.log(`CHAT_${id}_REQUESTING_BUSY`);
      return Promise.resolve(state.byId[id]);
    }

    // Return cached chat if it's less than 1 minute old
    const cachedChat = state.byId[id];
    if (cachedChat && Date.now() - cachedChat.lastFetched < 60 * 1000) {
      console.log(`CHAT_${id}_CACHED`);
      return Promise.resolve(cachedChat);
    }

    // Ensure chats list is loaded first, with error handling
    return dispatch(this.getChats())
      .then(() => {
        console.log(`CHAT_${id}_REQUESTING`);
        dispatch({ type: 'CHAT_LOADING', payload: { id } });

        return fetch(`/api/chats/${id}`)
          .then(response => {
            if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
            return response.json();
          })
          .then(chatData => {
            console.log(`CHAT_${id}_REQUESTED`);
            const updatedChat = { ...chatData, lastFetched: Date.now() };
            dispatch({
              type: 'CHAT_LOADED',
              payload: { id, chat: updatedChat }
            });
            return updatedChat;
          })
          .catch(error => {
            console.error(`CHAT_${id}_REQUEST_FAILED`, error);
            dispatch({ type: 'CHAT_FAILED', payload: { id, error } });
            throw error;
          });
      })
      .catch(error => {
        console.error('Failed to load chats list; cannot fetch single chat', error);
        throw error;
      });
  };
}

3. Standardized State Structure

Update your initial state to support these optimizations—this makes state management clearer and avoids ambiguity:

const initialChatsState = {
  list: [],
  byId: {}, // Stores individual chat details by ID for quick access
  isLoading: false,
  chatLoadings: {}, // Tracks loading state for each chat ID
  lastFetched: null,
  error: null,
  chatErrors: {}, // Stores errors for individual chat requests
  abortController: null // For cancelling in-flight list requests
};

4. Request Cancellation

The AbortController in the updated getChats lets you cancel requests when they're no longer needed (e.g., when a component unmounts). Add cleanup logic in your components:

// In a React component using useEffect
useEffect(() => {
  const fetchChats = async () => {
    try {
      await dispatch(getChats());
    } catch (err) {
      // Handle error
    }
  };

  fetchChats();

  // Cancel request on unmount
  return () => {
    const abortController = store.getState().chats.abortController;
    abortController?.abort();
  };
}, [dispatch]);

Final Thoughts

Your initial logic has a good foundation for preventing duplicate requests, but it lacks flexibility and robustness for real-world concurrent scenarios. With the optimizations above, you'll get:

  • Controlled caching with support for re-fetching
  • Full error handling for both list and individual chat requests
  • Prevention of duplicate concurrent requests at both the list and chat level
  • Protection against race conditions via request cancellation

内容的提问来源于stack exchange,提问作者Kindzoku

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:48:11