krasimir / stent

Stent is combining the ideas of redux with the concept of state machines

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

generator with 'takeLatest' - is canceling and debouncing possible?

alp82 opened this issue · comments

You showed an example with generators:

const machine = Machine.create('todo-app', {
  state: { name: 'idle', todos: [] },
  transitions: {
    'idle': {
      'fetch todos': function * () {
        yield { name: 'fetching' };

        try {
          const todos = yield call(getTodos, '/api/todos');
        } catch (error) {
          return { name: 'error', error };
        }

        return { name: 'done', todos };
      }
    }
  }
});

If I would perform machine.fetchTodos() twice while the async call is still ongoing, the second one is simply discarded.

  • How to model a machine that cancels the first one and fetches again?
  • How to debounce such executions?

I'll try to release a new version these days. Funny enough I hit the same problem. I just needed takeLatest.

I just stumbled upon this project due to your great article and this was the first thing that came to my mind when taking a glance at your README.

Yep, that's definitely a valid use case. I'm going to add something which will work like takeLatest.

Ok, just released 3.0.0 version that supports a latest version of the method. So for example if the machine has a fetchTodos method you may use:

machine.fetchTodos.latest();

This will kill a previous call which is still in progress. However, I have to make a note here. Let's see the following redux-saga code:

function test (n) {
  return {
    type: 'TEST',
    n
  };
}
store.runSaga(function * () {
  yield takeLatest('TEST', function * ({ n }) {
    console.log(n);
    setTimeout(() => console.log('you can not stop me'), 2000);
    console.log('promise: ' + (yield call(a, n)));
  });
});
store.runSaga(function *() {
  yield put(test(1));
  yield put(test(2));
});

/*
1 <-- immediately
2 <-- immediately
promise 2 <-- a second later
you can not stop me <-- two seconds later
you can not stop me <-- two seconds later
*/

So we have a saga that waits for an action. Once this action comes we do something (the first console.log) synchronous and then do two other things which are asynchronous. The difference between them is that the first one setTimeout(() => console.log('you can not stop me'), 2000); is not in a control of redux-saga library while the second one is. As we can see from the produced console output we get that first async operation fired no matter what. That is because redux-saga has no idea about this process and can not stop it. It is the same with Stent. As long as the async process is wrapped in a call execution we have .latest working. Otherwise then canceling does not work.

That new version of Stent support takeLatest-ish logic which is fine for cases where we don't bother making the request twice. However if you want to implement a debounce and avoid making multiple requests I'll suggest to do it upfront and not rely on Stent for it. Just debounce the action dispatching.