redux 传奇,throttle/debounce 有条件?

redux saga, throttle/debounce conditionally?

我正在记录横幅在屏幕上可见时的展示次数。

当用户滚动时,同一横幅可以在短时间内多次显示。

我想避免这种情况。

乍一看,throttle 是防止它的完美方法。

但是当您在一个页面中有多个横幅时,如果受到限制,throttle 将不会在屏幕上记录第二个横幅。

那么我如何限制每个键? (横幅 id 作为本例中的键) 即,我想限制每个 banner_id 的横幅印象。 (这就像服务器限制每个 api 键的 api_endpoint 访问)

编辑

可以考虑为每个键创建 throttle,但不知道它是否会占用太多资源?

我想知道 API 库(例如 Django-rest-framework)如何实现每个 api 键的节流。我想这可能与saga throttle的作用完全不同。

使用可过期地图

django-rest 这样的东西使用 expirable map 进行节流。一个过期的 set 也足以完成任务。

不幸的是,我不能为过期 map/set 推荐一个确切的 npm 模块。

伪代码:

function* throttlePerKey(pattern, selector, timeout, saga) {
  const set = new ExpirableSet({ expire: timeout })

  while(true) {
    const action = yield take(pattern)
    const id = selector(action)
    const throttled = set.has(id)
    if (throttled) {
      // Do nothing, action throttled
    } else {
      set.add(id)
      yield call(saga, action)
    }
  }
}

只使用 redux-saga

我们可以用 redux-saga 模拟过期集,得到一个纯粹的 redux-saga 解决方案。

代码:

function* throttlePerKey(pattern, selector, timeout, saga) {
  const set = new Set()

  while(true) {
    const action = yield take(pattern)
    const id = selector(action)
    const throttled = set.has(id)
    if (throttled) {
      // Do nothing, action throttled
    } else {
      set.add(id)
      // Expire items after timeout
      yield fork(function* () {
        yield delay(timeout)
        set.delete(id)
      })
      yield call(saga, action)
    }
  }
}

Andrey 的 redux-saga 解决方案很棒。我稍微修改了它以包含尾随调用。对于我的用例(多人文档编辑器),我需要它在进行更改后立即发送数据,但也在油门延迟结束时发送数据,以便始终发送最新数据。

function* throttlePerKey(pattern, selector, timeout, saga) {
    const map = new Map()

    while (true) {
        const action = yield take(pattern)
        const id = selector(action)
        const throttled = map.has(id)
        if (throttled) {
            // track how many attempts there were
            map.set(id, map.get(id) + 1)
        } else {
            map.set(id, 1)
            // Expire items after timeout
            yield fork(function* () {
                yield delay(timeout)
                // Call at the end if there were multiple attempts
                if (map.get(id) > 1) {
                    yield call(saga, action)
                }
                map.delete(id)
            })
            // Call immediately on first attempt
            yield call(saga, action)
        }
    }
}