Basic concept: Asynchronous programming solution
Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions.
1. Grammar:Generator
The function is aThe state machine
Encapsulates multiple internal states
Executing Generator returns an traverser object
In addition to the state machine, the Generator function is also an iterator object Generator
Execution of Generator returns an traverser object, that is, Generator is also a traverser object Generator in addition to the state machine. A traverser object that iterates through each state within the Generator in turn.
2. Form:Generator
A function is an ordinary function, but it has two characteristics
(1) Function keyword and function name between oneThe asterisk
;
(2) Internal use of function bodyyield
Expression that defines different internal states (yield
In English, it means"Output"
)
3, callGenerator
After the function:You must call
Of the traversal objectNext method
To move the pointer to the next state
This function does not execute and does not return the result of a function run. Instead, it returns a pointer Object to the internal state, the Iterator Object described in the previous chapter.
(1) Each callnext
Method, the inner pointer executes from the head of the function or where it last stopped until the next one is encounteredyield
Expression (orreturn
Statement) until
(2) The Generator functions are executed piecewise, the yield expression is a token to pause execution, and the next method can resume execution
Next, the next method of the traverser object must be called to move the pointer to the next state. That is, each time the next method is called, the internal pointer executes from the head of the function or where it was last stopped until the next yield expression (or return statement) is encountered. In other words, Generator functions are executed piecewise, yield expressions are paused tokens, and the next method can resume execution.
function* aaa(){
yield 'hello';
yield 'world';
return 'ending';
}
var hw=aaa();
console.log(aaa);
//[GeneratorFunction: aaa]
console.log(hw);
//Object [Generator] {}
console.log(hw.next());
// { value: 'hello', done: false }
console.log(hw.next());
// { value: 'world', done: false }
console.log(hw.next());
// { value: 'ending', done: true }
console.log(hw.next());
// { value: undefined, done: true }
Copy the code
The above function has three states: hello, world, and return statements (end execution).
(3)next
Method returns an object whosevalue
The property is the currentyield
The value of the expression,done
The value of the property indicates that the traversal is not complete
(4) UntilReturn statement
(ifNo return statement
, they performAt the end of the function
)
(5)next
The object returned by theThe value attribute
, is to keep up withAfter the return statement
The value of the expression of (ifThere is no return
Statements,value
The value of the attributeFor undefined
)
(6)done
The value of the attributeTo true
Is displayed, indicating that the traversal is complete
4. The asterisk between the function keyword and the function name is not specified
/ / will do
function * foo(x, y) {...}function *foo(x, y) {...}function* foo(x, y) {...}function*foo(x, y) {...}Copy the code
Yield expression
Because the Generator returns a traverser object that only calls to the next method iterate over the next internal state, it actually provides a function to pause execution.
The yield expression is the pause flag
Run logic for the next method of the traversal object
(1) Encounteryield
The expression isPause subsequent operations
And will followyield
The value of the following expression as the value of the returned objectvalue
Attribute values
(2) Next callnext
Method, and then continue untilThe next yield is encountered
expression
(3) IfThere is no
Meet againThe new yield
The expression isRun until the end of the function
Until thereturn
Statement, and willreturn
The value of the expression following the statement as the value of the returned objectvalue
Attribute values
(4) If the functionThere is no return
Statement of the object returnedValue The value of the attribute is undefined
(5)yield
expressionThe following expression
, only when callednext
Method, an internal pointer to the statementWill perform
So it’s equal to zeroJavaScript
Manual is provided"Lazy evaluation"
(Lazy Evaluation) syntax capabilities
function* gen() {
yield 123 + 456;
}
//yield 123 + 456 is not evaluated immediately, but only when the next method moves the pointer to this line.
Copy the code
Yield expressions and return statements
There are similarities and differences.
The similarity is that
: can return the value of the expression immediately following the statement
The difference is that
: Every timeyield
, the function suspends execution and continues backward from that position the next time, whilereturn
Statements do not have the function of location memory
Inside a function, only executeReturn at a time (or one)
Statement, but can be executedYield several times (or more)
expression
Without yield expressions, the Generator function becomes a pure deferred function
function* f() {
console.log('Executed! ')}var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
Copy the code
Yield expressions can only be used in Generator functions
If you use it anywhere else, you’ll get an error.
(function (){
yield 1; }) ()// SyntaxError: Unexpected number
Copy the code
If the yield expression is used in another expression, it must be enclosed in parentheses
function* demo() {
//console.log('Hello' + yield); // SyntaxError
//console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
var a=demo();
console.log(a.next()); //{ value: undefined, done: false }
//Helloundefined
console.log(a.next()); //{ value: 123, done: false }
//Helloundefined
console.log(a.next());//{ value: undefined, done: true }
Copy the code
Yield expressions are used as function arguments or to the right of assignment expressions, without parentheses
var foo=function(a,b){
console.log(a);
console.log(b);
}
// Demo has three yields, so it takes four next done times to equal true
function* demo() {
foo(yield 'a'.yield 'b'); // OK
let input = yield; // OK
console.log(input);
}
var a=demo();
console.log(a.next()); //{ value: 'a', done: false }
console.log(a.next()); //{ value: 'b', done: false }
console.log(a.next()); // This step is executed by foo
//undefined
// undefined
// { value: undefined, done: false }
console.log(a.next()); //undefined
// { value: undefined, done: true }
Copy the code
Relationship to the Iterator interface
1. Of any objectSymbol.iterator
Method,Is equal to the
The object ofThe traverser generates functions
, calling the function willreturn
The object ofAn traverser object
2, due to theGenerator
functionThat's the traverser generator function
, so we can putGenerator
Assigned to the objectSymbol.iterator
Attribute, thusmake
Have toThis object has an Iterator interface
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] / / [1, 2, 3]
Copy the code
3,Generator
The function returns an iterator object after execution. The object itself also hasSymbol.iterator
Properties,Symbol.iterator
Property after executionReturn to their own
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
Copy the code
Parameters to the next method
1.The yield expression itself returns no value, or always returns undefined
(This is why yield variables are always undefined.)
2. Code following the last yield (the next line, not the code immediately following the yield) is executed only after the next next
3,next
A method can take an argument that is treated asOn a yield
The expression of theThe return value
(Change the last yield and the statements immediately following yield to arguments to next)
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
console.log(reset);
if(reset) { i = -1; }}}var g = f();
console.log(g.next()); // { value: 0, done: false }
console.log(g.next()); //undefined
// { value: 1, done: false }
console.log(g.next()); //undefined
// { value: 2, done: false }
console.log(g.next(true)); // true
//{ value: 0, done: false }
Copy the code
If the next method has no arguments, the value of the reset variable is always undefined each time the yield expression is run.
When the next method takes a parameter true, the reset variable is reset to that parameter (true), so I equals -1 and the next loop increments from -1 to 0.
throughnext
The parameters of the method enter different values at different stages
1.Generator
The context state of a function from paused to resumed (context
) is constant
2, through thenext
Method parameters, there is a method inGenerator
Once the function starts running,Continue to inject values into the function body
That is, the behavior of the Generator function can be adjusted by injecting different values from outside to inside at different stages of its operation.
3, due to thenext
The argument to the method represents the return value of the previous yield expression, so inThe first time you use the next method, passing parameters is invalid
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()); // Object{value:6, done:false} y=undefined
console.log(a.next()); // Object{value:NaN, done:false} Equal to z = = NaN undefined / 3
console.log(a.next()); // Object{value:NaN, done:true} = 5+NaN+undefined
var b = foo(5);
console.log(b.next()); // { value:6, done:false } y=undefined
console.log(b.next(12)); // {value:8, done:false} y=2*12=24 z=undefined
console.log(b.next(13)); // {value:42, done:true} z
Copy the code
for… The of loop no longer calls the next method
for… The of loop automatically iterates over the Iterator generated when the Generator function is running, and there is no need to call the next method.
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1, 2, 3, 4, 5
Copy the code
Make native objects support for… Of, which is traversed by a Generator function
Native JavaScript objects have no traversal interface and cannot use for… The of loop is ready to be used by adding this interface to it via a Generator function.
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield[propKey, obj[propKey]]; }}let jane = { first: 'Jane'.last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
Copy the code
willGenerator
Function added to the objectSymbol.iterator
Property above.
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]]; }}let jane = { first: 'Jane'.last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
Copy the code
Extended operator (.
), destruct assignment andArray.from
Method can be used toGenerator
Function returnedIterator
Object as a parameter
Extended operators (…) , destruct assignment, and array. from are all called inside the iterator interface. This means that they can both take an Iterator returned by a Generator function as an argument.
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// Extend the operator
[...numbers()] / / [1, 2]
/ / Array. From method
Array.from(numbers()) / / [1, 2]
// Destruct the assignment
let [x, y] = numbers();
x / / 1
y / / 2
// for... Of circulation
for (let n of numbers()) {
console.log(n)
}
/ / 1
/ / 2
Copy the code
The Generator. The prototype. Throw () function in vitro throw an error, then inGenerator
Function capture in vivo
Each traverser object returned by a Generator has a throw method that throws an error outside the function and catches it inside the Generator.
var g = function* () {
try {
yield;
} catch (e) {
console.log('Internal capture', e); }};var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('External capture', e);
}
// Internally capture a
// External capture b
Copy the code
In the code above, the traverser object I throws two consecutive errors. The first error is caught by a catch statement inside the Generator. I Throws an error the second time. Since the catch statement inside the Generator has already been executed and will not be caught again, the error is thrown into the Generator body and caught by the catch statement outside the Generator.
1.throw
The method can take an argument that is calledcatch
Statement received, recommended to throwError
Object instance
var g = function* () {
try {
yield;
} catch (e) {
console.log(e); }};var i = g();
i.next();
i.throw(new Error('Wrong! '));
// Error: Error! (...).
Copy the code
2. Do not confuse the traverser objectthrow
Method and globalthrow
The command
The error in the code above is thrown by the throw method of the traverser object, not by the throw command. An error thrown by a global throw command can only be caught by a catch statement outside the function.
The throw command is independent of the g.row method and does not affect each other.
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if(e ! ='a') throw e;
console.log('Internal capture', e); }}};var i = g();
i.next();
try {
throw new Error('a');
throw new Error('b');
} catch (e) {
console.log('External capture', e);
}
// External capture [Error: a]
Copy the code
3. Generator functionsinternal
No try is deployed… Catch block, sothrow
Method throws errors that will be externaltry... catch
Code block capture
var g = function* () {
while (true) {
yield;
console.log('Internal capture', e); }};var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('External capture', e);
}
// External capture a
Copy the code
4. Generator functionsInternal and external
, do not deploy try… Catch block then the program will report an error, directly interrupt execution
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined
Copy the code
5. Errors thrown by the throw method must be caught internally if the next method has been executed at least once
function* gen() {
try {
yield 1;
} catch (e) {
console.log('Internal capture'); }}var g = gen();
g.throw(1);
// Uncaught 1
Copy the code
In the code above, the next method is not executed once when g.row (1) is executed. Instead of being caught internally, the thrown error is thrown directly externally, causing the program to fail. This behavior is easy to understand, because the first execution of next is equivalent to starting the internal code of the Generator function, otherwise the Generator has not started execution, and the throw error can only be thrown outside the function.
6. Once the throw method is caught, the next yield expression is executed
That is, a next method is executed as an accompaniment
As long as the Generator function has a try deployed internally… Catch block, so that an error thrown by the iterator’s throw method does not affect the next iteration.
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
Copy the code
7. An error thrown by a Generator can also be caught by a catch outside the Generator
function* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
Copy the code
The second next method passes a parameter 42 to the function body, which has no toUpperCase method, so it throws a TypeError that is caught by a catch outside the function body.
Once an error is thrown during Generator execution and is not caught internally, it will not proceed
Once an error is thrown during Generator execution and is not caught internally, it will not proceed.
If the next method is called later, an object with value equal to undefined and done equal to true is returned, indicating that the JavaScript engine considers the Generator to be finished running.
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke! ');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('Run the next method for the first time', v);
} catch (err) {
console.log('Catch errors', v);
}
try {
v = generator.next();
console.log('Run the next method a second time', v);
} catch (err) {
console.log('Catch errors', v);
}
try {
v = generator.next();
console.log('Run the next method a third time', v);
} catch (err) {
console.log('Catch errors', v);
}
console.log('caller done');
}
log(g());
// starting generator
// Next {value: 1, done: false}
// throwing an exception
{value: 1, done: false}
// Next {value: undefined, done: true}
// caller done
Copy the code
This code runs the next method three times, the second time throwing an error, and the third time the Generator is finished.
The Generator. The prototype. The return () returns the given value, and put an end to traverse the Generator function
The traverser object returned by the Generator function, and a return method that returns the given value and terminates the traversal of the Generator function.
1, ifreturn
Method called with no arguments, returns the valuevalue
Properties forundefined
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
Copy the code
2. After calling return, call next, value is always undefined, and the done attribute always returns true
In the code above, after the iterator object G calls the return method, the value attribute of the return value is the argument foo of the return method. In addition, the Generator’s traversal terminates, and the done attribute returns true. Next calls always return true.
3, ifGenerator
Inside the function istry... finally
Code block, and executingtry
Code block, soreturn
The method will be deferred tofinally
Execute the code block after it has been executed
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
Copy the code
In the above code, the finally block is executed after the return method is called, and then the return method is executed after the finally block is finished.
Yield * expression ifyield
The expression is followed by an traverser object that is required in theyield
So let’s add to this expressionThe asterisk
, indicating that it returns an traverser object
1, inGenerator
Function inside, callAnother Generator
Function. You need to manually complete the traversal yourself inside the former function body
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// Manually traverse foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
Copy the code
2,ES6
providesyield*
The expression, as a solution, is used in aGenerator
Inside the functionExecute another Generator
function
(1)yield*
At the back of theGenerator
Function (nonereturn
Statement), equivalent toGenerator
Inside the function, deploy onefor... of
cycle
(2)yield*
At the back of theGenerator
Function in areturn
Statement is requiredvar value = yield* iterator
Form acquisition ofreturn
The value of the statement
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
/ / is equivalent to
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
/ / is equivalent to
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
Copy the code
From a syntactic point of view, if a yield expression is followed by an traverser object, you need to add an asterisk after the yield expression to indicate that it returns an traverser object. This is called the yield* expression.
function* inner() {
yield 'hello! ';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // Returns a traverser object
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
Copy the code
Yield * is followed by an array (string), which, since arrays (strings) natively support iterators, iterates over group members
// The yield command returns the entire array without an asterisk, and the asterisk indicates that the array traversator object is returned.
function* gen(){
yield* ["a"."b"."c"];
}
var n=gen();
console.log(n.next()); // { value:"a", done:false }
console.log(n.next()); // { value:"b", done:false }
console.log(n.next()); // { value:"c", done:false }
console.log(n.next()); // { value:undefined, done:true }
The yield expression returns the entire string, and the yield* statement returns a single character. Because strings have an Iterator interface, they are iterated by yield*
function* gen(){
yield* 'hello';
}
var n=gen();
console.log(n.next()); // { value:"h", done:false }
console.log(n.next()); // { value:"e", done:false }
console.log(n.next()); // { value:"l", done:false }
console.log(n.next()); // { value:'l', done:true }
console.log(n.next()); // { value:'o', done:true }
console.log(n.next()); // { value:undefined, done:true }
Copy the code
A proxied Generator function that has a return statement can be proxied to itsGenerator
Function returns data
function* foo(){
yield 2;
yield 3;
return 'foo';
}
function* bar(){
yield 1;
var v = yield* foo();
console.log('v' + v);
yield 4;
}
var it = bar();
console.log(it.next()); //{ value: 1, done: false }
console.log(it.next()); //{ value: 2, done: false }
console.log(it.next()); //{ value: 3, done: false }
console.log(it.next()); //vfoo
//{ value: 4, done: false }
console.log(it.next()); //{ value: undefined, done: true }
Copy the code
function* genFuncWithReturn() {
yield 'a';
yield 'b';
return 'The result';
}
function* logReturned(genObj) {
let result = yield* genObj;
console.log(result);
}
[...logReturned(genFuncWithReturn())]
// The result
// The value is ['a', 'b']
Copy the code
There are two traversals. The first is the iterator object returned by the extended operator iterator function logReturned, and the second is the iterator object returned by the yield* statement iterator genFuncWithReturn. The effect of these two traversals is superimposed, culminating in the traversal object returned by the extended operator function genFuncWithReturn. So, the final data expression yields a value equal to [‘a’, ‘b’]. However, The result returned by The return statement of genFuncWithReturn is returned to The result variable inside logReturned, so there is terminal output.
The yield* command is a convenient way to extract all members of a nested array
function* iterTree(tree) {
if (Array.isArray(tree)) {
for(let i=0; i < tree.length; i++) {
yield* iterTree(tree[i]); }}else {
yieldtree; }}const tree = [ 'a'['b'.'c'], ['d'.'e']].for(let x of iterTree(tree)) {
console.log(x);
}
// a
// b
// c
// d
// e
Copy the code
A Generator function as an object property
If the property of an object is a Generator function, you can abbreviate it as follows.
let obj = {
* myGeneratorMethod(){...}};Copy the code
Its full form is as follows and is equivalent to the above.
let obj = {
myGeneratorMethod: function* () {
/ /...}};Copy the code
This of the Generator function
An instance of a Generator function inherits methods from the Prototype object of the Generator function
Generator functions always return an traverser, which ES6 defines as an instance of Generator functions and inherits methods from Generator function prototype objects.
function* g() {}
g.prototype.hello = function () {
return 'hi! ';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi! '
Copy the code
2. Generator functions cannot be constructors either
If you treat g as a normal constructor, it doesn’t work because g always returns a traverser object, not this.
function* g() {
this.a = 11;
}
let obj = g();
obj.next();
obj.a // undefined
Copy the code
3. Generator functions cannot be used with the new command, which generates an error
function* F() {
yield this.x = 2;
yield this.y = 3;
}
new F()
// TypeError: F is not a constructor
Copy the code
4. Have the Generator return a normal instance of an object, either using the next method or a normal this
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a / / 1
f.b / / 2
f.c / / 3
Copy the code
Generator and state machine
The Generator is the best structure for implementing a state machine.
The following clock function is a state machine
//es5
var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick! ');
else
console.log('Tock! '); ticking = ! ticking; }//es6
// The loop generates Tick and Tock because while is always true
var clock = function* () {
while (true) {
console.log('Tick! ');
yield;
console.log('Tock! ');
yield;
}
}();
console.log(clock.next());
console.log(clock.next());
console.log(clock.next());
console.log(clock.next());
// Tick!
// { value: undefined, done: false }
// Tock!
// { value: undefined, done: false }
// Tick!
// { value: undefined, done: false }
// Tock!
// { value: undefined, done: false }
Copy the code
application
(1) Synchronous expression of asynchronous operation
The pause effect of Generator functions means that asynchronous operations can be written in yield expressions and executed later when the next method is called. This is essentially equivalent to not having to write the callback function, because subsequent operations of the asynchronous operation can be put under the yield expression until the next method is called anyway. Therefore, an important practical use of Generator functions is to handle asynchronous operations and override callback functions.
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI(); // The loadUI function is called without actually doing anything
/ / load the UI
loader.next() // First time next
/ / uninstall the UI
loader.next() // Next
Copy the code
The first time the loadUI function is called, it does not execute and only returns a traverser.
The next time you call the next method on this traverser, the Loading interface (showLoadingScreen) is displayed and data is loaded asynchronously (loadUIDataAsynchronously).
When the data is loaded and the next method is used again, the Loading interface will be hidden.
The Generator function deploys Ajax operations
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response); // Next must have an argument otherwise result in main is undefined
});
}
var it = main();
it.next();
Copy the code
Read the text file line by line through the Generator function
function* numbers() {
let file = new FileReader("numbers.txt");
try {
while(! file.eof) {yield parseInt(file.readLine(), 10); }}finally{ file.close(); }}Copy the code
(2) Control flow management
If you have a multi-step operation that is very time consuming.
The callback function
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// Do something with value4
});
});
});
});
Copy the code
Promise
Promise.resolve(step1)
.then(step2)
.then(step3)
.then(step4)
.then(function (value4) {
// Do something with value4
}, function (error) {
// Handle any error from step1 through step4
})
.done();
Copy the code
The Generator function
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1);
var value3 = yield step2(value2);
var value4 = yield step3(value3);
var value5 = yield step4(value4);
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4}}Copy the code
Then, all the steps are automatically executed in sequence using a function.
scheduler(longRunningTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// Continue if Generator is not finished
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
Copy the code
This is only applicable to synchronous operations, that is, all tasks must be synchronous and no asynchronous operations can be performed. Because the code here continues as soon as it gets the return value, it doesn’t know when the asynchronous operation is complete.
Use for… The of loop automatically executes the yield feature in turn, providing a more general approach to control flow management
(Can only be used when all steps are synchronous)
// Array steps encapsulates the steps of a task
let steps = [step1Func, step2Func, step3Func];
// The Generator function iterateSteps adds yield to each step in turn
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yieldstep(); }}/ / for... The of loop executes all the steps of each task in sequence at once
for (var step of iterateJobs(jobs)){
console.log(step.id);
}
Copy the code
for... of
The essence of awhile
Loop, so the above code essentially executes the following logic
var it = iterateJobs(jobs);
var res = it.next();
while(! res.done){var result = res.value;
// ...
res = it.next();
}
Copy the code
(3) UseGenerator
The delta function can be atAny object
Deployed onIterator
interface
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield[key, obj[key]]; }}let myObj = { foo: 3.bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
Copy the code
Deploy the Iterator interface to arrays
function* makeSimpleGenerator(array){
var nextIndex = 0;
while(nextIndex < array.length){
yieldarray[nextIndex++]; }}var gen = makeSimpleGenerator(['yo'.'ya']);
gen.next().value // 'yo'
gen.next().value // 'ya'
gen.next().done // true
Copy the code
(4) As a data structure
Generator
Can be seen asThe data structure
To be more precise, it can be regarded as aArray structure
Because a Generator function can return a series of values, this means that it can provide an array-like interface to any expression.
function* doStuff() {
yield fs.readFile.bind(null.'hello.txt');
yield fs.readFile.bind(null.'world.txt');
yield fs.readFile.bind(null.'and-such.txt');
}
Copy the code
The above code returns three functions in sequence, but using Generator functions allows you to process the three returned functions as if they were arrays.
for (task of doStuff()) {
// Task is a function that can be used like a callback function
}
// Equivalent to ES5
function doStuff() {
return [
fs.readFile.bind(null.'hello.txt'),
fs.readFile.bind(null.'world.txt'),
fs.readFile.bind(null.'and-such.txt')]; }Copy the code