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

如何实现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:

Approach Overview

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 catchError block if you want to stop the entire batch on the first failure.
  • Retry logic: If you need to retry failed saves, add a retry operator (e.g., retry(3)) before catchError in the API call pipe.
  • UI feedback: Use the batchSaveStatus, savedVideos, and failedVideos state to show progress bars, success messages, or error lists in your UI.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 09:46:24