如何实现Action循环执行直至全部成功?批量视频更新Epic技术咨询
Hey there! Let's work through how to make your Redux-Observable epic handle batch video saves sequentially until all updates are successfully completed. Here's a practical, step-by-step solution:
The core idea is to manage a queue of videos to save, and use RxJS's expand operator to recursively process each video in the queue—triggering a save request for one video, waiting for it to complete, then moving on to the next until the queue is empty. We'll use a dedicated batch action to kick off the process, and track progress via Redux state.
Step 1: Define Clear Action Types
First, define action types to handle the batch workflow:
// Action type constants export const SAVE_VIDEOS_BATCH = 'SAVE_VIDEOS_BATCH'; export const SAVE_VIDEO_SUCCESS = 'SAVE_VIDEO_SUCCESS'; export const SAVE_VIDEO_FAILURE = 'SAVE_VIDEO_FAILURE'; export const ALL_VIDEOS_SAVED = 'ALL_VIDEOS_SAVED';
Step 2: Dispatch a Batch Trigger Action
Instead of dispatching individual saveVideo actions on submit, dispatch a single batch action that carries all the modified videos:
// When the user clicks Submit const handleSubmit = () => { dispatch({ type: SAVE_VIDEOS_BATCH, payload: { videos: modifiedVideosList } // Array of videos with updated titles }); };
Step 3: Implement the Batch Save Epic
Here's the epic code that handles sequential processing using expand:
import { ofType } from 'redux-observable'; import { concatMap, expand, map, catchError, of } from 'rxjs'; import { saveVideoApi } from './your-api-service'; // Your API call to save a single video export const updateVideoEpic = (action$) => action$.pipe( ofType(SAVE_VIDEOS_BATCH), // Start processing the first video in the batch concatMap(({ payload: { videos } }) => { if (videos.length === 0) { return of({ type: ALL_VIDEOS_SAVED }); } const [currentVideo, ...remainingVideos] = videos; return saveVideoApi(currentVideo).pipe( map(() => ({ type: SAVE_VIDEO_SUCCESS, payload: { video: currentVideo, remainingVideos } })), catchError((error) => { // Handle individual save failures (adjust based on your needs) console.error(`Failed to save video ${currentVideo.id}:`, error); // Option 1: Continue with remaining videos return of({ type: SAVE_VIDEO_FAILURE, payload: { video: currentVideo, remainingVideos, error } }); // Option 2: Stop processing on first failure // return of({ type: BATCH_SAVE_FAILED, payload: { error } }); }) ); }), // Recursively process remaining videos expand((action) => { // If we have remaining videos to process if ([SAVE_VIDEO_SUCCESS, SAVE_VIDEO_FAILURE].includes(action.type)) { const { remainingVideos } = action.payload; if (remainingVideos.length === 0) { return of({ type: ALL_VIDEOS_SAVED }); } const [currentVideo, ...nextRemaining] = remainingVideos; return saveVideoApi(currentVideo).pipe( map(() => ({ type: SAVE_VIDEO_SUCCESS, payload: { video: currentVideo, remainingVideos: nextRemaining } })), catchError((error) => { console.error(`Failed to save video ${currentVideo.id}:`, error); return of({ type: SAVE_VIDEO_FAILURE, payload: { video: currentVideo, remainingVideos: nextRemaining, error } }); }) ); } // Stop recursion when we hit the final completion action return of(); }) );
Step 4: Update Your Reducer for State Tracking
Add reducer logic to track the batch save progress, success/failure status, and remaining videos:
const initialState = { batchSaveStatus: 'idle', // idle | in-progress | completed | completed-with-errors savedVideos: [], failedVideos: [], remainingVideos: [] }; export const videoReducer = (state = initialState, action) => { switch (action.type) { case SAVE_VIDEOS_BATCH: return { ...state, batchSaveStatus: 'in-progress', savedVideos: [], failedVideos: [], remainingVideos: action.payload.videos }; case SAVE_VIDEO_SUCCESS: return { ...state, savedVideos: [...state.savedVideos, action.payload.video], remainingVideos: action.payload.remainingVideos }; case SAVE_VIDEO_FAILURE: return { ...state, failedVideos: [...state.failedVideos, action.payload.video], remainingVideos: action.payload.remainingVideos }; case ALL_VIDEOS_SAVED: return { ...state, batchSaveStatus: state.failedVideos.length > 0 ? 'completed-with-errors' : 'completed', remainingVideos: [] }; default: return state; } };
Key Notes on Error Handling
- Continue on failure: The example above keeps processing remaining videos even if one fails. Adjust the
catchErrorblock if you want to stop the entire batch on the first failure. - Retry logic: If you need to retry failed saves, add a
retryoperator (e.g.,retry(3)) beforecatchErrorin the API call pipe. - UI feedback: Use the
batchSaveStatus,savedVideos, andfailedVideosstate to show progress bars, success messages, or error lists in your UI.
内容的提问来源于stack exchange,提问作者MarJano

