Generators

Usually, functions return one statement. Generator functions came to change this by allowing the function to exit the execution and re-enter again later maintaining the variable bindings across the re-entrances. The generator functions will return a generator object. The generator function looks like the following.

function* gen() {...}

For the generator function to execute its body, the next() method should be called on the iterator function that is returned. Then to maintain the execution, the generator function will be executed all the statements till it reaches a particular statement called yield.

The next call of the generator function will either continue the execution or simply not be depending on the existence another yield. Let's illustrate that with an example.

function* generator() {
    yield 'Hello,';
    yield 'World!';
}

const gen = generator();

gen.next(); // {value: "Hello,", done: false}
gen.next(); // {value: "World!", done: false}
gen.next(); // {value: undefined, done: true}

Each one of these returned objects, known as the generator objects, have a property named value that was returned by the yield, and another property named done that represents the status of the generator of it finished or still has some other data to be yielded. We can also return a value at the end of the generator function using the return statement.

function* generator() {
    yield 'Hello,';
    yield 'World!';
    return 'the end';
}

const gen = generator();

gen.next(); // {value: "Hello,", done: false}
gen.next(); // {value: "World!", done: false}
gen.next(); // {value: "the end", done: true}

Notice how the return statement has been treated as if it was a yield statement in the generator function except that the end property had a value of true. It indicates that the value that has been returned at the end is the final and is not from a yield.

Let's use the generator function to build something that could be useful. A use case of the generator functions is when we require making a nested AJAX requests. We make a request to get some data, and then depending on that data, we make another request(s) and so on. That could be daunting, especially when using callbacks where we would end up with callbacks hell. Now I know that we are better than that and we are using promises or async/await, but bear with me for this example. I'm going to use Star Wars API for the demonstration.

const API = 'https://swapi.co/api/';

function* SWRequests(id) {
    const character = yield sendRequest(`${API}people/${id}/`);
    console.log(character);
    const homeworld = yield sendRequest(character['homeworld']);
    console.log(homeworld);
}

function sendRequest(url) {
    fetch(url).then(data => data.json()).then(data => LukeSkywalker.next(data));
}

let LukeSkywalker = SWRequests(1);
LukeSkywalker.next();

First, we defined the API URL as a const. Then we defined the generator function; SWRequests(id). It will take an id of the character that we will fetch the data for.

Each yield will execute the function sendRequest(url). That function takes an url and uses the fetch API to send AJAX requests. We get the data and JSONfiy it, and we get another promise that where we will call the next() on the generator function. That will trigger the generator again, and send another request, just this time it will be using the character['homeworld'] that just happen to represent a URL.