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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

function*generateEvenNumbers(){

yield0;

yield2;

yield4;

yield6;

yield8;

yield10;

}

let evenNumbers=generateEvenNumbers();

console.log(evenNumbers.next());

//>{value: 0, done: false}

console.log(evenNumbers.next());

//>{value: 2, done: false}

console.log(evenNumbers.next());

//>{value: 4, done: false}

console.log(evenNumbers.next());

//>{value: 6, done: false}

console.log(evenNumbers.next());

//>{value: 8, done: false}

console.log(evenNumbers.next());

//>{value: 10, done: false}

console.log(evenNumbers.next());

//>{value: undefined, done: true}

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

1

2

let evenNumbers=generateEvenNumbers();

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

1

2

console.log(evenNumbers.next());

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

1

2

yield0;

and that value is the result of the next method

1

2

//>{value: 0, done: false}

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

1

2

yield2;

and that value is the result of the next method

1

2

//>{value: 2, done: false}

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

1

2

//>{value: undefined, done: true}

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.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

function*generateEvenNumbers(){

let zero=yield0;

let two=yield zero+2;

let four=yield two+2;

let six=yield four+2;

let eight=yield six+2;

yield eight+2;

}

let evenNumbers=generateEvenNumbers();

let result=evenNumbers.next();

console.log(result);

//>{value: 0, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 2, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 4, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 6, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 8, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 10, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: undefined, done: true}

The first call to the next method

1

2

let result=evenNumbers.next();

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

1

2

let zero=yield0;

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

1

2

3

console.log(result);

//>{value: 0, done: false}

The second call to the next method

1

2

result=evenNumbers.next(result.value);

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

1

2

let two=yield zero+2;

which is the result of the next method

1

2

3

console.log(result);

//>{value: 2, done: false}

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 throwgenerator method. If in the previous example we do something like

1

2

evenNumbers.throw('a new error condition');

execution will abruptly end with that error

1

2

//>VM4027:1 Uncaught a new error condition

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

function*generateEvenNumbers(){

let zero;

try{

zero=yield0;

}catch(error){

console.log(error);

zero=0;

}

let two=yield zero+2;

let four=yield two+2;

let six=yield four+2;

let eight=yield six+2;

yield eight+2;

}

let evenNumbers=generateEvenNumbers();

let result=evenNumbers.next();

console.log(result);

//>{value: 0, done: false}

result=evenNumbers.throw("Something bad happened along the way");

console.log(result);

//>Something bad happened along the way

//>{value: 2, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 4, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 6, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 8, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: 10, done: false}

result=evenNumbers.next(result.value);

console.log(result);

//>{value: undefined, done: true}

The first call to the next method

1

2

let result=evenNumbers.next();

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

1

2

let zero=yield0;

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

1

2

3

console.log(result);

//>{value: 0, done: false}

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

1

2

result=evenNumbers.throw("Something bad happened along the way");

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

1

2

3

4

5

6

7

8

try{

zero=yield0;

}catch(error){

console.log(error);

zero=0;

}

let two=yield zero+2;

which is the result of the next method

1

2

3

console.log(result);

//>{value: 2, done: false}

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

1

2

3

4

5

6

7

8

9

10

11

12

*onAddDateReadingSessionClick(){

try{

yield validateDateReadingSession(this.state.dateReadingSession);

yield createDateReadingSession(this.props.bookUuid,

this.state.currentReadingSession.uuid,

this.state.dateReadingSession);

this.successOnAddDateReadingSession();

}catch(error){

this.errorOnApiOperation(error);

}

}

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