“拆分”动作通道/处理程序

时间:2018-11-26 15:31:14

标签: typescript redux-saga

给出此操作类型:

interface SaveFoo {
  type: 'SAVE_FOO'
  payload: {
    id: string
    value: number
  }
}

我想创建一个有选择地限制处理程序的传奇。因此,例如,如果调度了以下操作:

  1. { type: 'SAVE_FOO', payload: { id: "a", value: 1 } }
  2. { type: 'SAVE_FOO', payload: { id: "b", value: 1 } }
  3. { type: 'SAVE_FOO', payload: { id: "a", value: 2 } }
  4. { type: 'SAVE_FOO', payload: { id: "a", value: 3 } }

我想启动12的处理程序(因为它们具有不同的id属性),但是将34分配给缓冲区直到1完成处理为止。

感觉这应该是一个很普遍的用例,但是我找不到任何相关的东西。我尝试过手动执行,但是我觉得必须有更好的方法:

  export function splitThrottle<T>(actionCreator: ActionCreator<T>, saga: (action: Action<T>) => SagaIterator, selector: (payload: T) => string) {
    const tasks: Record<string, Task> = {}
    const bufferLookup: Record<string, Buffer<Action<T>>> = {}

    function* queue(action: Action<T>, id: string) {
      try {
        yield call(saga, action)
      } catch (e) {
        // don't propagate
      }

      const next = bufferLookup[id].take()
      if (next) {
        tasks[id] = yield call(queue, next, id)
      } else {
        delete tasks[id]
      }
    }

    return function* () {
      while (true) {
        const action: Action<T> = yield take(actionCreator)
        const id = selector(action.payload)

        const existingTask = tasks[id]
        if (existingTask) {
          bufferLookup[id].put(action)
        } else {
          let buffer = bufferLookup[id]
          if (!buffer) {
            buffer = buffers.sliding(1)
            bufferLookup[id] = buffer
          }
          tasks[id] = yield fork(queue, action, id)
        }

      }
    }
  }

1 个答案:

答案 0 :(得分:1)

这就是我实现它的方式。几乎相同的解决方案,但有一些不同的原语:

export function* splitThrottle(pattern, saga, selector) {
  const channels = {}

  while (true) {
    const action = yield take(pattern)
    const id = selector(action)
    const { channel, justCreated } = obtainChannel(channels, id)

    yield put(channel, action)
    if (justCreated) {
      yield fork(processAllAndDelete, channels, id, saga)
    }
  }
}

function obtainChannel(channels, id) {
  let channel = channels[id]
  if (channel) {
    return { channel, justCreated: false }
  } else {
    channel = channels[id] = channel(buffers.expanding(1))
    return { channel, justCreated: true }
  }
}

function* processAllAndDelete(channels, id, saga) {
  const channel = channels[id]
  while (true) {
    const actions = yield flush(channel)
    if (!actions)
      break

    for (const action of actions) {
      yield call(saga, action)
    }
  }
  delete channels[id]
}