ES6 introduced Generator functions, which can suspend the execution flow of a function through the `yield` keyword, making it possible to change the execution flow and thus providing a solution for asynchronous programming. Basic Usage

Generator Function Composition

Generator has two parts that distinguish it from ordinary functions:

  • One is that after `function` and before the function name, there is a `*`;

  • Inside the function there is a `yield` expression.

Among them, `*` is used to indicate that the function is a Generator function, and `yield` is used to define the internal state of the function.

function* func(){ console.log("one"); yield '1'; console.log("two"); yield '2'; console.log("three"); return '3'; }

Execution Mechanism

Calling a Generator function is the same as calling a regular function: just add parentheses after the function name. However, a Generator function does not execute immediately like a regular function; instead, it returns a pointer to an internal state object. Therefore, to call the `next` method of the iterator object, the pointer will start executing from the beginning of the function or from where it last stopped.

f.next(); // one // {value: "1", done: false} f.next(); // two // {value: "2", done: false} f.next(); // three // {value: "3", done: true} f.next(); // {value: undefined, done: true}

When calling the `next` method for the first time, execution starts from the beginning of the Generator function. First it prints `one`, then stops when reaching `yield`, and uses the value of the expression after `yield`, `'1'`, as the `value` property of the returned object. At this point the function has not finished executing, so the `done` property of the returned object is `false`.

When calling the `next` method for the second time, it is the same as the previous step.

When calling the `next` method for the third time, it first prints `three`, then performs the function's return operation, and uses the value of the expression after `return` as the `value` property of the returned object. At this point the function has ended, so the `done` property value is `true`.

When calling the `next` method for the fourth time, the function has already finished executing, so it returns `undefined` as the `value` property value and `true` as the `done` property value. If there were no `return` statement during the third step, it would directly return `{value: undefined, done: true}`.

Methods of the Iterator Object Returned by the Function

`next` method

In general, when the `next` method is called without arguments, the return value of the `yield` expression is `undefined`. When `next` is passed an argument, that argument will serve as the return value of the previous `yield`.

function* sendParameter(){ console.log("start"); var x = yield '2'; console.log("one:" + x); var y = yield '3'; console.log("two:" + y); console.log("total:" + (x + y)); }

Calling `next` without arguments

var sendp1 = sendParameter(); sendp1.next(); // start // {value: "2", done: false} sendp1.next(); // one:undefined // {value: "3", done: false} sendp1.next(); // two:undefined // total:NaN // {value: undefined, done: true} nextPassing argumentsvar sendp2 = sendParameter(); sendp2.next(10); // start // {value: "2", done: false} sendp2.next(20); // one:20 // {value: "3", done: false} sendp2.next(30); // two:30 // total:50 // {value: undefined, done: true}

In addition to using `next`, you can also use a `for...of` loop to iterate over the Iterator object generated by a Generator function.

`return` method

The `return` method returns the given value and ends iteration of the Generator function.

When the `return` method is provided with an argument, it returns that argument; when no argument is provided, it returns `undefined`.

function* foo(){ yield 1; yield 2; yield 3; } var f = foo(); f.next(); // {value: 1, done: false} f.return("foo"); // {value: "foo", done: true} f.next(); // {value: undefined, done: true} throwMethodthrowThe method canGeneratorthrow an exception outside the function body, and catch it inside the function body.var g = function* () { try { yield; } catch (e) { console.log('catch inner', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('catch outside', e); } // catch inner a // catch outside b

The iterator object throws two errors. The first one is caught inside the Generator function. The second one, because the `catch` inside the function body has already executed, will not be caught again, so this error is thrown out of the Generator function body and caught by the `catch` outside the function body.

`yield*` expression

The `yield*` expression means that `yield` returns an iterator object, which is used to call another Generator function inside a Generator function.

function* callee() { console.log('callee: ' + (yield)); } function* caller() { while (true) { yield* callee(); } } const callerObj = caller(); callerObj.next(); // {value: undefined, done: false} callerObj.next("a"); // callee: a // {value: undefined, done: false} callerObj.next("b"); // callee: b // {value: undefined, done: false} //Equivalent to function* caller() { while (true) { for (var value of callee) { yield value; } } }

Use Cases

Implementing Iterator

Provides a traversal method for objects that do not have an Iterator interface.

function* objectEntries(obj) { const propKeys = Reflect.ownKeys(obj); for (const propKey of propKeys) { yield [propKey, obj[propKey]]; } } const jane = { first: 'Jane', last: 'Doe' }; for (const [key,value] of objectEntries(jane)) { console.log(`${key}: ${value}`); } // first: Jane // last: Doe

`Reflect.ownKeys()` returns all properties of an object, regardless of whether the properties are enumerable, including Symbols.

`jane` natively does not have an Iterator interface and cannot be traversed with `for...of`. Here, a Generator function is used to add an Iterator interface, so the `jane` object can be traversed.