使用惯用的redux observable排队和取消事件

Eam*_*onn 6 rxjs redux rxjs5 redux-observable

给出了下载管理器的示例.可以有任意数量的活动下载.

可以调度操作以启动,停止,标记完成并标记任何特定下载的下载进度.

const START_DOWNLOAD = "START_DOWNLOAD";
const startDownload = payload => ({ type: START_DOWNLOAD, payload });

const DOWNLOAD_PROGRESS = "DOWNLOAD_PROGRESS";
const downloadProgress = payload => ({ type: DOWNLOAD_PROGRESS, payload });

const STOP_DOWNLOAD = "STOP_DOWNLOAD";
const stopDownload = payload => ({ type: STOP_DOWNLOAD, payload });

const COMPLETE_DOWNLOAD = "COMPLETE_DOWNLOAD";
const completeDownload = payload => ({ type: COMPLETE_DOWNLOAD payload });
Run Code Online (Sandbox Code Playgroud)

这些操作将包含用于标识下载的ID,并可使用以下reducer修改redux状态:

const downloadReducer = (state = initialState, action) => {
  switch (action.type) {
    case STOP_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "IDLE",
        },
      };

    case START_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "IN_PROGRESS",
          progress: 0,
        },
      };

    case DOWNLOAD_PROGRESS:
      return {
        ...state,
        [action.payload.id]: {
          state: "IN_PROGRESS",
          progress: action.payload.progress,
        },
      };

    case COMPLETE_DOWNLOAD:
      return {
        ...state,
        [action.payload.id]: {
          state: "DONE",
          progress: 100,
        },
      };

    default:
      return state;
  }
};
Run Code Online (Sandbox Code Playgroud)

现在出现了如何使用redux observable管理这些操作的异步调度的问题.

例如,我们可以这样做:

const downloadEpic = action$ =>
  action$.ofType(START_DOWNLOAD).mergeMap(action =>
    downloader
    .takeUntil(
      action$.filter(
        stop =>
        stop.type === STOP_DOWNLOAD &&
        stop.payload.id === action.payload.id,
      ),
    )
    .map(progress => {
      if (progress === 100) {
        return completeDownload({
          id: action.payload.id
        });
      } else {
        return downloadProgress({
          id: action.payload.id,
          progress
        });
      }
    }),
  );
Run Code Online (Sandbox Code Playgroud)

这有效.但是,如果我们想限制允许的活动下载数,该怎么办?我们可以更换mergeMapconcatMap只允许一次一个活跃的下载.或者我们可以提供并发参数,mergeMap并准确指定我们想要允许的内部下载器可观察的执行次数.

但是,这带来的问题是我们现在无法停止排队下载.

我已经创建了一个完整的工作示例,您可以在这里尝试.

如何以最惯用的方式使用rxjs和redux observable限制和排队下载?

jay*_*lps 4

我已经关注这个问题好几天了,但没有时间用完整的代码给你一个可靠的答案,因为它相对复杂。所以我只会给你 tl;dr 版本,我希望它比没有好:)


我的直觉告诉我,我会让 UI 调度一个代表尝试下载的操作,而不是真正的保证。例如ATTEMPT_DOWNLOAD。史诗将侦听此操作并检查当前的活动下载数量是否超过 >= 最大值,如果是,则将发出一个操作以将下载排入队列而不是启动下载。

您的减速器将存储那些活动的下载 ID 和排队的下载 ID。例如

{ active: ['123', '456', ...etc], queued: ['789'] }
Run Code Online (Sandbox Code Playgroud)

您可以使用它来专门跟踪活动/排队,还可以了解它们的计数active.lengthqueued.length

当下载完成时,某个地方会检查是否有任何排队的下载,如果有,则将其出队。具体如何做主要取决于个人喜好。例如,如果史诗发出DEQUEUE_DOWNLOAD或其他什么。

如果发送了取消操作,您的减速器将需要同时查找activequeued删除 ID(如果存在)。如果它实际上是活动的而不只是排队,那么处理下载的史诗将侦听取消操作,并且它将停止它并执行与上面相同的出队检查。


这有点令人费解,但最大的收获是:

  • 你的 UI 组件在尝试之前不应该知道事物将如何或何时排队,尽管它可以通过查看 redux 状态来了解事实(例如显示“下载排队”或其他)
  • 请小心不要在多个位置重复下载状态。例如,如果它处于活动状态,则您的商店中只有一个事实来源表明它处于活动状态
  • 史诗在减速器之后运行,利用这一点来发挥你的优势。
  • 可能会有一个史诗般的人,他唯一的工作就是监听真正的下载请求并执行它,而不知道任何有关排队或类似的东西。然后,该史诗将被多个其他史诗重用。可以直接将其作为函数调用,也可以发出像真实的动作一样的动作START_DOWNLOAD

业务逻辑应该位于何处并不总是很清楚。例如,reducer 应该主要是 getter/setter,还是应该有更多固执己见的逻辑并做出决策?在这些情况下没有太多规则。尽量保持一致。

顺便说一句,这完全是我的直觉。我可能已经发现(或者您可能仍然发现),实际上这不是一个好的解决方案。只是给出我的初步想法!