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.
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
getChatsaction checksisLoadingbefore 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
getChatwait forgetChatsto 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
- Permanent Cache Block: The
isRequestedflag is a one-time toggle. Once the initialgetChatsrequest completes, any subsequent calls togetChatswill immediately resolve without re-fetching data. This breaks use cases like pull-to-refresh or when backend data updates. - Missing Error Handling: There's no catch block for failed
fetchcalls ingetChats, andgetChatdoesn't handle cases wheregetChatsfails. In concurrent scenarios, this can leave your app in a broken state with no feedback. - No Per-Chat Request Guard:
getChatdoesn't check if a specific chat is already loading, so multiple concurrent calls for the same chat ID will fire duplicate requests. - Uncancelled Requests: If a component unmounts or the user navigates away mid-request, the ongoing
fetchwill still complete and dispatch actions, leading to race conditions (e.g., updating state for a component that no longer exists).
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]);
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

