Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using redux-saga with setInterval - how and when to yield

Having just moved from thunks to sagas I'm trying to find the best way to call setTimeout and then from within that function call another function (in this case corewar.step()). This was my original code which works as I'd expect.

  runner = window.setInterval(() => {

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

    operations += processRate;

  }, 1000/60)

This code is inside a saga and I believe that I should be able to wrap function calls within call as I've done in other areas in the application.

I've tried wrapping the setInterval call in a call and leaving everything else as it is, which results in step() never being called.

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

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

    operations += processRate;

  }, 1000/60)

I've tried, leaving the setInterval as it is and wrapping the step() function in a call and changing the anonymous function signature to function* which also results in step() never being called.

  runner = window.setInterval(function*() {

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

    operations += processRate;

  }, 1000/60)

Finally, I've tried wrapping both, which again results in step() never being called.

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

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

    operations += processRate;

  }, 1000/60)

It feels like I'm missing something here so my question is, should I need to wrap these functions up in call at all or is this wrong?

The follow on question if I am supposed to wrap the outer setInterval in a call would be how should I be defining a function as a parameter to call which also wants to yield either a put or call itself?

like image 333
dougajmcdonald Avatar asked Dec 31 '17 17:12

dougajmcdonald


People also ask

How do you use yield call in redux saga?

import { call } from 'redux-saga/effects' function* authorize(user, password) { try { const response = yield call(/** Api call */, user, password) ... } catch(error) { ... } } If you don't want to use yield , you can directly call you API with params using axios or fetch . Hope this helps you.

What does yield do in redux saga?

Sagas are implemented as Generator functions that yield objects to the redux-saga middleware. The yielded objects are a kind of instruction to be interpreted by the middleware. When a Promise is yielded to the middleware, the middleware will suspend the Saga until the Promise completes.

When should I use redux saga?

Redux Saga is a middleware library used to allow a Redux store to interact with resources outside of itself asynchronously. This includes making HTTP requests to external services, accessing browser storage, and executing I/O operations. These operations are also known as side effects.

What is difference between takeLatest and takeEvery?

takeEvery - enables the use of several fetchData objects at the same time. At a given moment, we can start a new fetchData task while there are still one or more previous fetchData tasks which have not yet terminated. takeLatest - Only one fetchData task can be active at any given moment.


4 Answers

There is a section in the saga-redux docs called "Using the eventChannel factory to connect to external events", that suggests using channels.

This section is also providing an example for a setInterval implementation:

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)
      }
    }
  )
}

You would then use yield call and yield takeEvery to set it up:

const channel = yield call(countdown, 10);
yield takeEvery(channel, function* (secs) {
    // Do your magic..
});
like image 83
madc Avatar answered Oct 13 '22 15:10

madc


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

This works pretty fine for me. In your second snippet there is a double ) at the end where should be only one.

like image 26
Krasimir Avatar answered Oct 13 '22 15:10

Krasimir


A little late to the party here but this is the top search result for the question of setting a timer in a saga. There's an alternate solution due to the nature of sagas. From here.

I adapted this so:

function* callSelfOnTimer({ value }) {
  // Do your work here
  ...
  // If still true call yourself in 2 seconds
  if (value) {
    yield delay(2000);
    yield call(callSelfOnTimer, { value });
  }
}
like image 33
GenericJam Avatar answered Oct 13 '22 15:10

GenericJam


For this to work you also need to add this:

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 });  
    }  
}
like image 39
Tom Glenn Avatar answered Oct 13 '22 16:10

Tom Glenn