Generator functions

The very last type of function definition style we will cover is the very powerful generator function. Broadly, generators are used to supply and control the iteration behavior for a sequence of one or more, or even infinite, items. 

Generator functions in JavaScript are specified with an asterisk following the function keyword:

function* myGenerator() {...}

When called, they will return a generator object, which uniquely conforms to both the iterable protocol and the iterator protocol, meaning that they can be iterated over themselves or can serve as an object's iteration logic.

Feel free to skip ahead to the section on the iterable protocolThe generator function makes far more sense when you think of it as a convenient way to create an iterator or iterable.

A generator function will halt and return a value at the point of a yield statement, and this can occur multiple times. After a yield, the function is effectively stalled while it waits for a consumer to need its next value. This is best illustrated with an example:

function* threeLittlePiggies() {
yield 'This little piggy went to market.';
yield 'This little piggy stayed home.';
yield 'This little piggy had roast beef.';
}

const piggies = threeLittlePiggies();

piggies.next().value; // => 'This little piggy went to market.'
piggies.next().value; // => 'This little piggy stayed home.'
piggies.next().value; // => 'This little piggy had roast beef.'

piggies.next(); // => {value: undefined, done: true}

As you can see, the generator object that's returned from the function has a next method, which, when called, will return an object with a value (indicating the current value of the iteration) and a done property (indicating whether the iteration/generation is complete). This is the iterator protocol and is the contract you can expect all generators to fulfill.

A generator fulfills not only the iterator protocol but also the iterable protocol, which means it can be iterated over by language constructs that accept iterables (such as for...of or the ...spread operator):

for (let piggy of threeLittlePiggies()) console.log(piggy); 
// => Logs: "This little piggy went to market."
// => Logs: This little piggy stayed home."
// => Logs: This little piggy had roast beef."

[...threeLittlePiggies()];
// => ["This little piggy went to market", "This little piggy stayed...", "..."]

Async generator functions can also be specified. They usefully combine the async and generator formats into a hybrid that allows for custom asynchronous generation logic, like so:

async function* pages(n) {
for (let i = 1; i <= n; i++) {
yield fetch(`/page/${i}`);
}
};

// Fetch five pages (/page/1, /page/2, /page/3)
for await (let page of pages(3)) {
page; // => Each of the 3 pages
};

You'll notice how we're using the for await iteration construct to iterate through our asynchronous generator. This will ensure that each iteration will await its result before continuing. 

Generator functions are very powerful, but it's important to be aware of the underlying mechanics at play. They are not regular functions and are not guaranteed to run to completion. Their implementation should take into account the context in which they will be run. If your generator is intended to be used as an iterator, then it should respect the implied expectations of iteration: that it is a read-only operation of an underlying piece of data or generation logic. While it is possible to mutate underlying data within a generator, this should be avoided.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.104.124