I’m using react, redux and redux-thunk in one of my learning projects and recently I wanted to try redux-saga and see in practice the advantages, if any, of using it instead of redux-thunk. I started with saga tutorial and I got confused about how generators are used to do async operations. Instead of jumping ahead and replace redux-thunk with redux-saga, I thought that it is much better to understand how generators can be used with promises without any other helper library.

In my learning project I have multiple branches and one of them uses only react with promises. A sample async code of one my components looks like

Both validateDateReadingSession and createDateReadingSession return promises so we can chain them with then and catch methods. How can we use generators to simplify this code? Before answering to this question we need to take a closer look on how generators work.

In this example we have a generator that return the first six even numbers. When we call the generator with

we obtain an iterator instance on which we can call the next method. At this moment the code inside the generator was not executed yet. The first call to the next method

causes the code inside the generator to run up to the first yield statement

and that value is the result of the next method

The second call to the next method resumes the execution inside the generator and continues up to the next yield statement

and that value is the result of the next method

This continues up to the last yield statement and after that each call to the next method will return

which states that there is no point to continue since we retrieved all possible values.

At this moment we can send values out of the generator and then wait for the next next method call but we also need to receive values in and to signal errors when execution resumes to be able to fully support async operations.

Sending values in the generator can be done by passing a value parameter to the next method and that value will be the result of the previous yield operation where execution resumed.

The first call to the next method

causes the code inside the generator to run up to the first yield statement

the returned value is the result of the next method and will be assigned to result variable.

The second call to the next method

resumes the execution inside the generator with the value returned earlier, variable zero is initialized with the value passed in and the next yield statement sends a new value out

which is the result of the next method

This continues up to the last yield statement like in the previous execution flow. Because the value passed in the generator replaces the value of the previous yield statement, there is no point to pass in a value for the first next method.

We have a way now to pass values in the generator when execution resumes, we need to be able to signal errors too. This is done by calling throw generator method. If in the previous example we do something like

execution will abruptly end with that error

One solution is to use try / catch in the generator and decide what to do next

The first call to the next method

causes the code inside the generator to run up to the first yield statement

the returned value is the result of the next method and will be assigned to result variable.

Now we don’t use next method but instead we signal an error with throw method

which resumes the execution inside the generator with the signalled error, that error is caught and logged and we start over with a new value for zero variable. Execution continues up to the next yield statement

which is the result of the next method

This continues up to the last yield statement like in the previous execution flows.

We have everything we need now, let’s see how we can change bellow code

to benefit from the usage of generators. As I mentioned before validateDateReadingSession and createDateReadingSession methods return promises. If we want to avoid the usage of callbacks in onAddDateReadingSessionClick method, we have to transform it in a generator and yield the promises

Note that there is no function statement in front of onAddDateReadingSessionClick because it is part of a component.
We broke the execution flow by yielding a promise but this is not enough, we need another piece of code that sends back the values in and resumes the execution of the generator

and a way to couple the runner with the generator

The execution flow is as follow:

  • onAddButtonClick is triggered and the code inside it’s event handler runs run(this.onAddDateReadingSessionClick)
  • with const iterator = generator(...params); functions generator is called with provided arguments
  • iterate(iterator.next()); does the following
    • iterator.next() is called first
    • validateDateReadingSession(this.state.dateReadingSession) is executed and the response (a promise), is yielded.
    • iterate method is called with that promise and then it chains it’s then or catch methods.
    • if the promise is resolved, we are calling the next method and we pass in the response of the promise.
    • if the promise is rejected, we are signalling the error by calling the throw method and the error will be caught by the catch block.
  • The flow repeats until there are no more yield statements.

This generator does not use the result of the promise. Bellow is another example that does this

I do not know if this way of implementing really pays off, I find it more a matter of preference. At least java programmers will be more comfortable with this code.

Your thoughts are welcome