Intro to generators

Generators are new type of function, that would come someday in ES standard. Generators are quite similar to simple functions, but have some extra features: their execution could be suspended, they can pass intermediate results of execution to outside environment and you’re able to pass some outside results inside or event throw some error and your function will continue to deal with such result or error.

For creating new generator we’re using new function-generator syntax function*. And when you run such function for the first time it doesn’t execute it’s body immediately and just returns specific instance of generator. Creation syntax looks like

After create generator stands on it’s initial state. The key method to work with generator is .next that starting generator and continue till coming across first yield operator. At that point generator suspends his state and returns value of definition which stands right after yield to outer function scope. In our example we just create new generator and trying to get first yield value:

In our case firstNumber gets 10 and generator suspends execution after till we use numbers.next() next time. The same will be for next two executions in which we get values 20 and 30 .

But question raises, when to stop execution? Is there some flag to understand that generator don’t have what to return any more? Yes. As you can see each time it returns object like {value: ..., done: ...} . And that’s is an answer for us. Last execution will look like:

The flag we need is done property of returned object, finally it’s value is true . Each next .next call would raise error and return {value: undefined, done: true} object.

There is no workaround to reset state of created generator. For now the only way is just to create new instance.

Two way data flow and throw errors

Please consider next example

While first .next call we’re getting back value with question text and saving it in outer scope under questionText variable. Within second .next call we pass name argument inside. This argument returns as result of yield expression and saved inside generator under his name variable and we’re able to operate him when moving further in the code and composing return value Hello ${name} .

But you’re able to pass inside not even value but error as well. You can do that with .throw method. This will call exception inside generator on line with yield . You can easily catch it with try{} catch{} block inside (example below (1)). On point (2) it should call alert in example.

If you don’t catch it in generator exception will bubble in stack outside of generator and start to crash your script with unhandled exception if no catch blocks outside generator as well.

Iteration of generators

You can easily iterate over generators because they are iterable objects (method .next saying that to us).

As you may already mention, for..of structure doesn’t return last value for us. It’s a specific case when for iteration we need use own implementation with while or just replace return with yield for last case as well.

You can easily create infinite generators and use them as generator of some data as well like while(true) { yield Math.random(); } in your generator body (but be careful with that and always keep some logic to stop your iteration).

Composition of generators

Each generator can contain child generators as well. There is special syntax included for doing that. Let’s have a look on example:

This form of yield* can be used only inside another generator and delegates execution to internal generator. Such composition works even if internal generators produce infinite chain of data.

Side effects and flat async code with generators

One from the main fields of using generators is building flat async chains of code. Main concept of this is:

yield should return Promises;

should return Promises; Generator should be served with special outside function called manager . This manager calls generator.next and gets new Promise each time. After resolving Promise, value is passed back to generator with next generator.next call.

. This manager calls and gets new Promise each time. After resolving Promise, value is passed back to generator with next call. With the last yielded value from generator with {done: true} manager handles it as result value of the whole logic chain.

Let’s consider next example:

Here asyncResult will collect a result value after all yield iterations finished. But let’s go step by step:

manager function gets generator instance as argument

function gets generator instance as argument Right after that manager calls .next method to get currentOrganization request promise and if it’s not last yield in generator and next.done !== true manager waits for promise resolving

method to get request promise and if it’s not last yield in generator and manager waits for promise resolving After promise is resolved it calls generator and passes resolved data as second argument. This data will be stored as yield return value.

Generator wakes up and continues to run its code till next yield operator.

The last call will return next.done === true to manager which is the flag to write next.value as a final result.

After words

Such approach helps to build async request chains effectively and helps to avoid callback hell and nesting hell of promises.

There are some libraries which using such approach under the hood. One of that is co. You can read more about implementations on official page.

The second library is more relevant for those ones, who are using redux single store concept solutions. It’s known as redux-saga. Awesome solution for managing application side effects which are easier to manage, more efficient to execute, simple to test, and better at handling failures. More about you can find from official page.