When we start to deal with ES6, the generators are almost at the end of the list of innovations that we pay attention to. Often, we simply perceive generators as an easy way to create custom iterators, but in fact they can provide us with much more interesting features and are most likely one of the most interesting innovations in ES6.
What are generators and how do they work?
Generators are functions that can be started, paused, and resumed at various stages of execution. Essentially, this special function returns an iterator. Functions generators are indicated by an asterisk after the keyword
function , and all magic is hidden in the use of the special keyword
yield . It determines that the iterator should return after the next call to the
next () method.
And immediately an example of a simple generator in ES6:
function* myGenerator() { yield 'first'; let input = yield 'second'; yield input; }
So what happens here?
')
- We declare the function generator using the special function * myGenerator () {} syntax .
- The first call to this function returns an iterator object. This object has a method next to resume the function of the generator in its current state.
- The generator function does not start its execution until we run iterator.next.
- Each time, when iterator.next is called, the function resumes its execution from the place of the last pause and executes all the code until it “stumbles” on the next yield and again pauses.
- A call to iterator.next returns an object containing the value that was passed to yield and a flag that indicates whether the function has completed its execution or not.
Generators as iterators
At the beginning of the article it was mentioned that often generators are used as a simple mechanism for creating iterators. All this can be done using
for for syntax:
For example:
function* myGenerator() { yield 'first'; yield 'second'; yield 'third'; } for (var v of myGenerator()) { console.log(v); }
Or for example:
function* myGenerator(start, stop) { for (var i = start; i < stop; i++) yield i; } for (var v of myGenerator()) { console.log(v); }
Ok, iterators are great, but let's see what things can be done more abruptly using generators.
So, we need to log in to some kind of backend, then use an authentication token to retrieve data from the API. If we apply the generator mechanism, it will look like this:
co(function* () { var result = yield login(username, password); var posts = yield getPosts(result.token); return posts; }).then(value => { console.log(value); }, err => { console.error(err); }); function login(username, password) { return fetch('/login', { method: 'post', body: JSON.stringify({ username: username password: password }) }).then(response => response.json()); } function getPosts(token) { return fetch('/posts', { headers: new Headers({ 'X-Security-Token': token }) }).then(response => response.json()); }
This example uses the
co library, which makes your asynchronous code look like synchronous using the promise mechanism. Here for each
yield, promise returns the result to the generator function and saves it to a variable.
Express vs Koa
Eeee, Koa? What is it all about?Koa is a
new generation framework (from the subtitle of the official website), which improves the middleware writing mechanism, using the same generators and the
co library, and thus saving your applications from the
hell of callbacks .
From a philosophical point of view, Koa seeks to “fix and replace the node,” and Express, on the contrary, “expand the node” (from the Koa documentation).
Middleware in Koa is built on the mechanism of generators and is a “cascade”. Simply put, the mechanism “downstream” (
downstream ) through all middleware is first launched, and then “upstream” through all middleware (
upstream ).
So, an example:
var koa = require('koa'); var app = koa();
This example will return to us 'Hello World', but first:
- The request will go through the middleware x-response-time to do everything that goes to the yield next line.
- Then, next yield will stop the current function and transfer control to another middleware logger , it will also do everything to its next yield.
- In the logger, yield next will transfer control to the latest middleware.
- He in turn will form the answer this.body = 'Hello World' .
- We don’t have more middleware to transfer downstream , so the upstream process will start. Those. each middleware resumes its work after yield next , thus implementing an “upstream” mechanism.
Cool in general, what else to say. More about
Koa can be viewed on the
official website and on
github .
Conclusion
ES6 generators are a powerful mechanism that allows you to write cleaner and more understandable asynchronous code. Instead of using a bunch of callbacks throughout the code, we can now write asynchronous code that looks similar to synchronous, but in fact with the help of generators and the
yield keyword, it “waits” for the asynchronous operation to complete.