使用redux-saga和setInterval - 何时以及如何yield?

16
刚从 thunk 转到 sagas,我正在尝试找到最佳方法来调用 setTimeout 并从该函数内部调用另一个函数(在本例中为 corewar.step())。这是我的原始代码,它按照我预期的方式工作。
  runner = window.setInterval(() => {

    for(let i = 0; i < processRate; i++) {
      corewar.step()
    }

    operations += processRate;

  }, 1000/60)

这段代码在一个中,我相信我应该能够像在应用程序的其他区域一样,在中包装函数调用。

我尝试将setInterval调用包装在中,并将其他内容保持不变,结果导致step()从未被调用。

  runner = yield call(window.setInterval, () => {

    for(let i = 0; i < processRate; i++) {
      corewar.step()
    }

    operations += processRate;

  }, 1000/60)

我尝试过将setInterval保留原样,并通过调用包装step()函数,同时更改匿名函数签名为function*,但这也导致step()从未被调用。

  runner = window.setInterval(function*() {

    for(let i = 0; i < processRate; i++) {
      yield call([corewar, corewar.step])
    }

    operations += processRate;

  }, 1000/60)

最后,我已经尝试将两者包装起来,但再次导致step()从未被调用。

  runner = yield call(window.setInterval, function*() {

    for(let i = 0; i < processRate; i++) {
      yield call([corewar, corewar.step])
    }

    operations += processRate;

  }, 1000/60)

感觉好像哪里不对,所以我的问题是:我是否需要完全将这些函数封装在call中,还是这样做是错误的?

接下来的问题是,如果我应该将外部的setInterval包裹在call中,那么我应该如何定义一个作为call参数的函数,该函数也希望返回一个put或者call呢?

4个回答

17
在saga-redux文档的一个章节中,名为"利用eventChannel工厂连接外部事件",建议使用channels
该章节还提供了一个setInterval实现示例。
import { eventChannel, END } from 'redux-saga'

function countdown(secs) {
  return eventChannel(emitter => {
      const iv = setInterval(() => {
        secs -= 1
        if (secs > 0) {
          emitter(secs)
        } else {
          // this causes the channel to close
          emitter(END)
        }
      }, 1000);
      // The subscriber must return an unsubscribe function
      return () => {
        clearInterval(iv)
      }
    }
  )
}

您可以使用yield callyield takeEvery来设置它:

const channel = yield call(countdown, 10);
yield takeEvery(channel, function* (secs) {
    // Do your magic..
});

10
const anotherSaga = function * () {
  const runner = yield call(setInterval, () => {
    console.log('yes');
  }, 1000);
  console.log(runner);
}

这对我来说运行得相当好。在你的第二段代码中,末尾有一个多余的),应该只有一个。


嗯,你说得对,我一定是尝试将生成器用作CB参数的奇怪组合(额外的括号只是一个笔误,在q中已经删除)。 - dougajmcdonald
真正的问题是你不能在这个函数内调用一个生成器,因为它永远不会被执行。你能做什么来每秒改变状态? - dtakis

5
虽然我有些晚到,但这篇文章是设置saga计时器问题的热门搜索结果。由于saga的特性,有一种替代方案。详情请参考这里
我将其改为:
function* callSelfOnTimer({ value }) {
  // Do your work here
  ...
  // If still true call yourself in 2 seconds
  if (value) {
    yield delay(2000);
    yield call(callSelfOnTimer, { value });
  }
}

1
要使此功能正常工作,您还需要添加以下内容:
const delay = (ms) => new Promise(res => setTimeout(res, ms))

function* callSelfOnTimer({ value }) {  
    // Do your work here  
    ...  
    // If still true call yourself in 2 seconds  
    if (value) {  
        yield delay(2000);  
        yield call(callSelfOnTimer, { value });  
    }  
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接