Detailed Generator_ basics in JavaScript ES6

Source: Internet
Author: User
Tags arrays exception handling generator iterable es6 class

I am very excited about the new features discussed today, because this feature is the most magical feature of ES6.

What does this "magic" mean? For beginners, this feature is completely different from the previous JS, and even some obscure. In a sense, it completely changes the normal behavior of the language, which is not "magic".

Not only that, this feature simplifies the program code and changes the complex "callback stack" to the form of a straight line execution.

Am I too much of a cushion? Now let's go into the details and judge for yourself.
Brief introduction

What is generator?

Look at the following code:

function* quips (name) {
 yield "Hello" + name + "!";
 Yield "I hope you are enjoying the blog posts";
 if (Name.startswith ("X")) {
  yield "it" cool how your name starts with X, "+ name;
 }
 Yield "you later!";
}
 
function* quips (name) {
 yield "Hello" + name + "!";
 Yield "I hope you are enjoying the blog posts";
 if (Name.startswith ("X")) {
  yield "it" cool how your name starts with X, "+ name;
 }
 Yield "you later!";
}

The code above is part of imitating talking cat (now a very popular application), click here to demo, and if you are confused about the code, come back here to see the explanations below.

This looks like a function, called the generator function, that has a lot in common with our common functions, but you can see the following two differences:

The usual function starts with a function, but the generator function starts with a function*.
Inside the generator function, yield is a keyword, and return is a bit like. The difference is that all functions (including the generator function) can only be returned once, and yield any time in the generator function. The yield expression suspends execution of the generator function and can then be resumed from the paused place.

The common function cannot suspend execution, and the generator function can, which is the biggest difference between the two.
Principle

What happens when the quips () is invoked?

> var iter = quips ("Jorendorff");
 [Object Generator]
> Iter.next ()
 {value: "Hello jorendorff!", done:false}
> Iter.next ()
 {value: "I hope you are Enjoyin G The blog posts ", done:false}
> Iter.next ()
 {value:" You later! ", done:false}
> Iter.next () 
   {value:undefined, done:true}

 
> var iter = quips ("Jorendorff");
 [Object Generator]
> Iter.next ()
 {value: "Hello jorendorff!", done:false}
> Iter.next ()
 {value: "I hope you are Enjo Ying the blog posts ", done:false}
> Iter.next ()
 {value:" You later! ", done:false}
> Iter.nex T ()
 {value:undefined, done:true}

We are very familiar with the behavior of ordinary functions, the function is called immediately executes, until the function returns or throws an exception, which is the second nature of all JS programmers.

The calling method of the generator function is the same as a normal function: quips ("Jorendorff"), but when the call to a generator function is not executed immediately, it returns a generator object (ITER in the code above), The function immediately pauses in the first line of the function code.

Each time the. Next () method of the Generator object is invoked, the function starts executing until the next yield expression is encountered.

That's why every time we call Iter.next () we get a different string, which is the value generated by the yield expression inside the function.

When the last Iter.next () is executed, the end of the generator function is reached, so the. Done property value of the result returns True and the. Value property value is undefined.

Now, back to Talking Cat's DEMO, try adding some yield expressions to the code to see what happens.

Technically speaking, whenever a yield expression is encountered by the generator function, the stack frame-local variable, function parameter, temporary value, and current execution position of the function are removed from the stack, but the generator object retains a reference to the stack frame, so the next time you call the. Next () method , you can resume and continue execution.

It is worth reminding that generator is not multi-threaded. In languages that support multithreading, multiple pieces of code can be executed at the same time, and with the competition of the execution resources, the uncertainty and better performance of the execution results. Unlike the generator function, when a generator function executes, it executes with its caller in the same thread, each execution order is determined, ordered, and the order of execution does not change. Unlike threads, the generator function can suspend execution at the internal yield flag point.

By introducing the pause, execution, and recovery execution of the generator function, we know what the generator function is, and now we're throwing a question: what's the use of the generator function?
Iterators

Through the previous article, we know that iterators are not a built-in class for ES6, but just as an extension point of language, you can define an iterator by implementing the [Symbol.iterator] () and. Next () methods.

However, implementing an interface still requires writing some code, let's take a look at how to implement an iterator in practice to implement a range iterator, which simply accumulates from one number to another, somewhat like a for (;;) loop in C language).

This should the "ding" three times
for (var value of range (0, 3)) {
 alert ("ding! At floor # "+ Value";
}
 
This should the "ding" three times
for (var value of range (0, 3)) {
 alert ("ding! At floor # "+ Value";
}

Now there is a solution, which is to use the ES6 class. (If you're not familiar with the class syntax, it doesn't matter, I'll introduce it in future articles.) )

Class Rangeiterator {
 constructor (start, stop) {
  this.value = start;
  This.stop = stop;

 [Symbol.iterator] () {return this;}

 Next () {
  var value = this.value;
  if (value < this.stop) {
   this.value++;
   return {done:false, value:value};
  } else {return
   {done:true, value:undefined}

}}} Return a The new iterator that counts the ' start ' to ' stop '.
function range (start, stop) {return
 new Rangeiterator (Start, stop);
}
 
Class Rangeiterator {
 constructor (start, stop) {
  this.value = start;
  This.stop = stop;
 
 [Symbol.iterator] () {return this;}
 
 Next () {
  var value = this.value;
  if (value < this.stop) {
   this.value++;
   return {done:false, value:value};
  } else {return
   {done:true, value:undefined}
 
}}} Return a The new iterator that counts the ' start ' to ' stop '.
function range (start, stop) {return
 new Rangeiterator (Start, stop);
}

View the DEMO.

This implementation is similar to the Java and Swift implementations and looks good, but can't say that the code is completely correct and the code has no bugs? It's hard to say. We don't see any traditional for (;;) Loop code: The iterator Protocol forces us to break the loop.

At this point, you may be less enthusiastic about iterators, they are easy to use, but it seems difficult to implement.

We can introduce a new way of implementation to make it easier to implement iterators. Can the generator described above be used here? Let's try:

function* Range (start, stop) {for
 (var i = Start I < stop; i++)
  yield i
}
 
function* Range (start, stop) {for
 (var i = Start I < stop; i++)
  yield i
}

View the DEMO.

The above 4 lines of code can completely replace the previous 23-line implementation, replacing the entire Rangeiterator class, because generator is a natural iterator, all generator are native implementations. Next () and [Symbol.iterator ] () method. All you need to do is to implement the loop logic.

Do not use generator to implement an iterator is like being forced to write a long, long message, it is simple to express your meaning, the implementation of rangeiterator is tedious and inexplicable, because it does not use the cyclic syntax to implement a circular function. The use of generator is what we need to grasp the implementation of the way.

What functions can we use as generator for iterators?

So that any object can be traversed-write a genetator function to traverse this, yield each pass to a value, and then use the generator function as the implementation of the [Symbol.iterator] method on the object to be traversed.
Simplifies functions that return arrays-if there is a function that returns an array every time it is invoked, for example:

Divide the one-dimensional array ' icons '
//into arrays of length ' rowlength '.
function splitintorows (icons, rowlength) {
 var rows = [];
 for (var i = 0; i < icons.length i + = rowlength) {
  Rows.push (icons.slice (i, i + rowlength));
 }
 return rows;
}

 
Divide the one-dimensional array ' icons '
//into arrays of length ' rowlength '.
function splitintorows (icons, rowlength) {
 var rows = [];
 for (var i = 0; i < icons.length i + = rowlength) {
  Rows.push (icons.slice (i, i + rowlength));
 }
 return rows;
}

You can use generator to simplify this type of function:

function* splitintorows (icons, rowlength) {for
 (var i = 0; i < icons.length; i = rowlength) {
  yield Icons.sli Ce (i, i + rowlength);
 }
 
function* splitintorows (icons, rowlength) {for
 (var i = 0; i < icons.length; i = rowlength) {
  yield Icons.sli Ce (i, i + rowlength);
 }

The only difference between the two is that the former computes all the results in the call and returns with an array, which returns an iterator, which is computed only when needed and then returned one by one.

Infinite result Set-we cannot construct an infinite array, but we can return a generator that generates endless sequences, and each caller can get any number of required values from it.
Refactoring complex loops-do you want to refactor a complex, lengthy function into two simple functions? Generator is a new Swiss army knife that you refactor in the Toolbox. For a complex loop, we can refactor the part of the generated dataset into a generator function, and then traverse it with for-of: for (var data of mynewgenerator (args)).
The tool that builds iterators-ES6 does not provide an extensible library for filtering and map operations on datasets, but generator can implement such functionality in a few lines of code.

For example, suppose you need to implement the same functionality as Array.prototype.filter on the nodelist. It's a piece of cake:

function* Filter (test, iterable) {for
 (var item of iterable) {
  if (test (item))
   yield item;
 }
}

 
function* Filter (test, iterable) {for
 (var item of iterable) {
  if (test (item))
   yield item;
 }
}

So, generator is very practical, right? Of course, this is the simplest way to implement a custom iterator, and in ES6, the iterator is the new standard for datasets and loops.

However, this is not the full function of generator.
Asynchronous Code

Asynchronous APIs usually require a callback function, which means that each time you need to write an anonymous function to handle the asynchronous result. If you handle three asynchronous transactions at the same time, we see three indented levels of code, not just three lines of code.

Look at the following code:

}. On ("Close", function () {Done
 (undefined, undefined);
}). On (' Error ', function (error) {done
 (error);
};
 
}). On ("Close", function () {Done
 (undefined, undefined);
}). On (' Error ', function (error) {done
 (error);
});

Asynchronous APIs usually have a convention of error handling, and different APIs have different conventions. In most cases, the error is discarded by default, and even some of the successes are discarded by default.

Until now, these problems are still the price we must pay to handle asynchronous programming, and we have accepted that asynchronous code is simply not as simple and friendly as synchronizing code.

Generator gives us hope that we can no longer use the above approach.

Q.async () is an experimental attempt to combine generator and Promise to handle asynchronous code, so that our asynchronous code is similar to the corresponding synchronization code.

For example:

Synchronous code to make some noise.
function Makenoise () {
 shake ();
 Rattle ();
 Roll ();
}

Asynchronous code to make some noise.
Returns a Promise object that becomes resolved
//Where we ' re done making noise.
function Makenoise_async () {return
 Q.async (function* () {
  yield shake_async ();
  Yield Rattle_async ();
  Yield Roll_async ();}
 );
 
Synchronous code to make some noise.
function Makenoise () {
 shake ();
 Rattle ();
 Roll ();
}
 
Asynchronous code to make some noise.
Returns a Promise object that becomes resolved
//Where we ' re done making noise.
function Makenoise_async () {return
 Q.async (function* () {
  yield shake_async ();
  Yield Rattle_async ();
  Yield Roll_async ();}
 );


The biggest difference is that you need to add the yield keyword before each asynchronous method call.

In Q.async, adding an If statement or Try-catch exception handling, as in the synchronized code, reduces a lot of learning costs compared to the way other asynchronous code is written.

Generator provides us with an asynchronous programming model that is better suited to the way the brain thinks. But better syntax may be more helpful, in ES7, a Promise and generator asynchronous processing function is being planned, inspired by similar features in C #.
compatibility

On the server side, you can now use generator directly in Io.js (or start Node with--harmony startup parameters in Nodejs).

On the browser side, currently only Firefox 27 and Chrome 39 version to support generator, if you want to use directly on the WEB, you can use Babel or Google's traceur to convert ES6 code to a web-friendly ES5 Code.

Some digression: JS version of the generator was first implemented by Brendan Eich, he borrowed from the realization of the Python generator, the realization of the inspiration from Icon, as early as the 2006 Firefox 2.0 to absorb the generator. But the standardization of the road is bumpy, all the way down, its syntax and behavior have changed a lot, Firefox and Chrome in the ES6 generator is implemented by Andy Wingo, the work is sponsored by Bloomberg.
yield;

There are still some generator that we have not covered. The use of throw () and. return () methods, the optional parameters of the. Next () method, and the yield* syntax. But I think this article has been long enough, like generator, we also pause, another time to find the rest of the part.

We have introduced two very important features of ES6, so it is now bold to say that ES6 will change our lives, seemingly simple features, but with great usefulness.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.