Why generator
On the front end development process, we often need to ask the backend data in data with brought to use operation, such as the web page rendering, however the request data is an asynchronous operation, and our page rendering it is synchronous operation, here in the ES6 generator can play its role, use it to write asynchronous code like write synchronization code. The following is an example, which will be described in more detail and ignored for the moment. If you already understand the basics of generator you can skip this section and the syntax section and go straight to the more in-depth section.
function *foo() {
// Request data
var data = yield makeAjax('http://www.example.com');
render(data);
}
Copy the code
How is it that the rest of the code is executed while waiting for data, and the rest of the code in Foo is not executed until data is returned? We all know that JS is single-threaded, which means it is impossible to execute two pieces of code at the same time. To achieve this effect, let’s guess (ps: Behind I’ve seen, of course, this is just to help you to understand, with questions to see), we have to assume that a “rod of the king” (refer to the executive power of CPU), who got the “rod of the king,” who can do what you want to do, now code execution to the foo we now take a “king” rod and then request data from the server, now the data has not returned, We can’t just wait. As Kings we have noble Marxist ideas, we first hand over their own rights, let the next person who needs to use it first, of course, the premise is that they agree to need for a long time, and then return the “scepter” to us. When the data comes back, we can get our scepter back and continue to do what we want to do. If you understand the process, congratulations, you have a basic understanding of how the Generator works. This analogy is not quite right, but it is basically the same idea. More things look down.
The generator grammar
The generator function
Before using Generator, we first need to understand its syntax. Function *foo() {} function* foo() {} function* foo() {} There is no difference between the two ways of writing. It is up to personal habit. I will use the first grammar in this article. We now declare a generator function in this syntax for later use.
function *foo() {}Copy the code
yield
So far we have been unable to do anything, for we are still lacking an important old yield. Yield translated into Chinese means to produce. Yield will let us follow the following expression, surrender control, and stop there until we call next(). We’ll skip next and talk about how generator executes. Let’s start with an example.
function *foo() {
var a = yield 1 + 1;
var b = yield 2 + a;
console.log(b);
}
var it = foo();
it.next();
it.next(2);
it.next(4);
Copy the code
First we define a generator function foo, then we execute it foo(), which, unlike normal functions, returns an iterator and waits for yield to be invoked. How to call that, which uses next we mentioned earlier, is to execute iterators one by one. Ok, so now we call the first it.next(), and the function will start at the beginning, and then at the first yield, it evaluates 1 + 1, well, and then it stops. Next (2). Notice that I pass in a 2 as an argument to next, which passes in a as its value. You may have many other questions, but we’ll get to them later. Next, our it.next(2) performs the second yield and computes 2 + a, which becomes 2 + 2 since a is 2. Next (4). The process is the same as in the previous step. We assign b to 4 and continue until we print out our b as 4. This is all the process the Generator performs. Now that you know what yield and next are doing, going back to the question, you might ask, why did I put a 2 and a 4 in next? Just to make it easier to understand, I manually computed 1 + 1 and 2 + 2, so where is the value that the program computed itself? Is it the return value of the next function? With that in mind, let’s look at the next section.
next
The parameters of the next
Next can pass in an argument that returns the last yield expression, just as it.next(2) will make a equal to 2. Of course, the first execution of next could also pass in an argument, but since it has no last yield, nothing can accept it and it will be ignored, so it doesn’t make sense.
The return value of next
In this section we will talk about the next return value. Without further ado, let’s print it out and see what it is. You can either execute it yourself or you can directly see the result of my execution.
function *foo() {
var a = yield 1 + 1;
var b = yield 2 + a;
console.log(b);
}
var it = foo();
console.log(it.next());
console.log(it.next(2));
console.log(it.next(4));
Copy the code
Execution Result:
{ value: 2, done: false }
{ value: 4, done: false }
4
{ value: undefined, done: true }
Copy the code
If you look at this, you can see that the result of the expression executed after yield does return, but only in the value field that returns the value, as well as what the done field is used for. Next (4) returns “done” : iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator: iterator True, all the preceding ones are false, so what’s undefined for the last printed value? Because we don’t have yield, so we’re not getting a value here, so how do we get the last one to have a value? It’s easy to just add a return. Let’s rewrite the example above.
function *foo() {
var a = yield 1 + 1;
var b = yield 2 + a;
return b + 1;
}
var it = foo();
console.log(it.next());
console.log(it.next(2));
console.log(it.next(4));
Copy the code
Execution Result:
{ value: 2, done: false }
{ value: 4, done: false }
{ value: 5, done: true }
Copy the code
The value of the last next is the value returned by the final return. At this point we don’t need to calculate our values manually, so let’s rewrite our example.
function *foo() {
var a = yield 1 + 1;
var b = yield 2 + a;
return b + 1;
}
var it = foo();
var value1 = it.next().value;
var value2 = it.next(value1).value;
console.log(it.next(value2));
Copy the code
And you’re done! This basically completes the basic part of the Generator. But there are more in-depth things we need to dig further, look, I believe you will have a harvest.
Deep understanding of
In the first two parts we learned why to use the generator and the syntax of the generator. These are the basics. In the next part we will look at something different.
- How to use this in asynchronous code, as the above examples are all synchronous
- How do YOU handle errors if they occur
- It’s too much trouble to call next one by one. Can we do that in a loop or automatically
The iterator
Before we move on to the next section let’s talk about iterators. By now we know that the generator returns an iterator. ES6 also provides a new iteration for… Of, for… “Of” allows us to directly iterate out each value, in the array it looks like this.
for (var i of ['a'.'b'.'c']) {
console.log(i);
}
// Output the result
// a
// b
// c
Copy the code
Let’s try it with our Generator iterator
function *foo() {
yield 1;
yield 2;
yield 3;
return 4;
}
// Get the iterator
var it = foo();
for(var i of it) {
console.log(i);
}
// Output the result
/ / 1
/ / 2
/ / 3
Copy the code
Now we find that… Of simply fetches the value returned each time we evaluate until done: true. Note here that our 4 is not printed, indicating for… The of iteration does not include the value when done is true.
Now let’s ask a new question, what happens if you execute generator inside generator? The new syntax, yield *, allows us to meet a generator executor at yield, and when a new generator needs to be executed, the yield will execute the new generator before continuing with our current generator. This might not be easy to understand, but let’s look at the code.
function *foo() {
yield 2;
yield 3;
yield 4;
}
function * bar() {
yield 1;
yield *foo();
yield 5;
}
for ( var v of bar()) {
console.log(v);
}
Copy the code
Here we have two generators and we execute foo in bar, we use yield * to execute foo, the order of execution here will be yield 1, then we encounter Foo into foo, and continue to execute yield 2 in foo until foo completes. Yield 5 is then returned to the bar.
One, two, three, four, fiveCopy the code
An asynchronous request
Our example above has always been synchronous, but our application is actually asynchronous, so let’s see how it works in asynchrony.
function request(url) {
makeAjaxCall(url, function(response) { it.next(response); })}function *foo() {
var data = yield request('http://api.example.com');
console.log(JSON.parse(data));
}
var it = foo();
it.next();
Copy the code
Back to the previous example, asynchronous requests surrender control at yield and then surrender control in a callback when the data callback is successful. So writing asynchronous code as if it were synchronous isn’t really synchronous, it’s just that the asynchronous callback process is encapsulated and invisible.
Error handling
We all know that in JS we use try… Catch to handle an error, similarly in generator, if an error occurs within the generator, if it can handle it internally, it is handled internally, and if it cannot, it continues bubbling outward until it can handle the error or the last layer.
Internal processing error:
// Internal processing
function *foo() {
try {
yield Number(4).toUpperCase();
} catch(e) {
console.log('error in'); }}var it = foo();
it.next();
// Result: error in
Copy the code
External processing error:
// External processing
function *foo() {
yield Number(4).toUpperCase();
}
var it = foo();
try {
it.next();
} catch(e) {
console.log('error out');
}
// Result: error out
Copy the code
Another special feature in generator error handling is that its iterator has a throw method that throws the error back to the generator, reporting an error where it paused, and then again, if it can handle it internally, bubbling if it can’t.
Internal processing results:
function *foo() {
try {
yield 1;
} catch(e) {
console.log('error', e);
}
yield 2;
yield 3;
}
var it = foo();
it.next();
it.throw('oh no! ');
// Error oh no!
Copy the code
External processing results:
function *foo() {
yield 1;
yield 2;
yield 3;
}
var it = foo();
it.next();
try {
it.throw('oh no! ');
} catch (e) {
console.log('error', e);
}
// Error oh no!
Copy the code
According to the test, the throw of the iterator is found to count as an iteration. The test code is as follows:
function *foo() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('error', e);
}
yield 3;
}
var it = foo();
console.log(it.next());
it.throw('oh no! ');
console.log(it.next());
// Run the result
// { value: 1, done: false }
// error oh no!
// { value: undefined, done: true }
Copy the code
When throwing an error back with a throw, except for the statement in a try, the iterator iterates over yield 3 and the next iteration is the final value. So there’s no more error handling. That’s it.
Run automatically
Can the generator operate automatically? Of course it can, and there are many such libraries, so let’s implement a simple one ourselves.
function run(g) {
var it = g();
// Use recursion to iterate
(function iterator(val) {
var ret = it.next(val);
// If there is no end
if(! ret.done) {/ / promise
if(typeof ret.value === 'object' && 'then' in ret.value) {
ret.value.then(iterator);
} else{ iterator(ret.value); }}}) (); }Copy the code
This allows us to automatically run our generator, which is very simple, with no error handling, and how to run multiple generators at the same time, which involves the transfer of control. I wrote a simple actuator Fo, which contains an example of Ping-pong by Kyle Simpson. If you are interested, you can see here is the portal, of course, you can easily star it, see you next article ~O(∩_∩)O~
Refer to the link
- The Basics Of ES6 Generators
- Diving Deeper With ES6 Generators
- Going Async With ES6 Generators
- Getting Concurrent With ES6 Generators