Created by Howard.Zuo / @leftstick
In computer science, a generator is a special routine that can be used to control the iteration behaviour of a loop. In fact, all generators are iterators.
From wikipedia
The function
keyword followed by an asterisk defines a generator function, which returns a Generator object:
Syntax
function* name([param[, param[, ... param]]]) {
statements
}
Let's try with simple snippet:
'use strict'
var idMaker = function* (){
var index = 0;
while(index < 3){
yield index++;
}
};
var gen = idMaker();
console.log(gen.next()); // value: 0, done: false
console.log(gen.next()); // value: 1, done: false
console.log(gen.next()); // value: 2, done: false
console.log(gen.next()); // value: undefined, done: true
Once the function starts running, it will always run to completion before any other JS code can run
'use strict'
setTimeout(function(){
console.log('Hello World');
}, 1);
var occupy = function() {
//You should never writing as following, since it's blocking the main thread
for (var i=0; i<=100; i++) {
console.log(i);
}
};
occupy();
// 0...100, take a long time than 1 millisecond
// 'Hello World' is printed right after occupy run to completion.
//The setTimeout cannot interrupt occupy() function while it's running
Generator is such a different function, which may be paused in the middle, one or multiple times, and resumed later, allowing other code to run during these paused periods.
Generator can:
'use strict'
var hello = function* () {
//step 3. The yield "world" expression will send the "world" string
//value out when pausing the generator function at that point
var str = (yield 'world');
return 'Hello ' + str;//step 5. final value returned
};
//step 1. get generator object, which conforms to both the iterator
//and the Iterable protocol
var gen = hello();
//step 2. get the value "world" from paused point where yield placed
console.log(gen.next());
//step 4. re-enter the generator with capitalized "World" passed in
//as the result of (yield 'world') expression
var finalResult = gen.next('World');
console.log(finalResult);//{ value: 'Hello World', done: true }
Name | Description |
---|---|
next() | Returns a value yielded by the yield expression. |
doesn't support in node
return()?
|
Returns the given value and finishes the generator. |
throw() | Throws an error to a generator. |
Syntax
gen.next(value)
Parameters
The value to send to the generator.Example
'use strict'
var gen = function*() {
while (true) {
var value = yield null;
console.log(value);
}
};
var g = gen();
console.log(g.next(1));//won't log anything in generator, since nothing yielding initially
console.log(g.next(2));//generator will log "2"
Syntax
gen.throw(exception)
exception
The exception to throw. For debugging purposes, it is useful to make it an instanceof Error.Example
'use strict'
var gen = function*() {
try {
yield 1;
} catch (e) {
console.log('ERROR: ', e);//step 3. get error thrown in step 2
}
};
var g = gen();
g.next(); //step 1. { value: 1, done: false }
g.throw(new Error('What the fuck!')); //step 2.
Syntax
[rv] = yield [expression];
expression
Defines the value to return from the generator function via the iterator protocol. If omitted, undefined is returned instead.rv
Returns the optional value passed to the generator's next() method to resume its execution.
'use strict'
var fibonacci = function*() {
var fn1 = 1, fn2 = 1;
while (true) {
var current = fn2;
fn2 = fn1;
fn1 = fn1 + current;
yield current;
}
};
var n;
for (n of fibonacci()) {
console.log(n);
// truncate the sequence at 1000
if (n >= 1000) {
break;
}
}
We've known few ways of handling async task:
Generator is sort of coroutine implementation in ES2015
With yield
's pausing ability
generator
and co(see below)
'use strict';
var co = require('co');
var getSession = function() {
return Promise.resolve({userId: '1234'});
};
var getUserById = function(userId) {
return userId ? Promise.resolve({
userId: userId,
name: 'hello'
}) : Promise.reject(new Error('no such user'));
};
var getCurrentUser = function*() {
try {
var session = yield getSession();//step 2.
var user = yield getUserById(session.userId);//step 4.
console.log('user is ', user);
} catch (e) {
console.error('ERROR:', e);
}
return user;
};
co(getCurrentUser);//execute the generator automatically
'use strict';
var getSession = function() {
return Promise.resolve({userId: '1234'});
};
var getUserById = function(userId) {
return userId ? Promise.resolve({
userId: userId,
name: 'hello'
}) : Promise.reject(new Error('no such user'));
};
var getCurrentUser = function*() {
try {
var session = yield getSession();//step 2.
var user = yield getUserById(session.userId);//step 4.
console.log('user is ', user);
} catch (e) {
console.error('ERROR:', e);
}
return user;
};
var it = getCurrentUser();
//step 1.the first yield push a promise out here
var sessionPromise = it.next().value;
sessionPromise
.then(function(session) {
//once we get the result, pass it back as final result of session in generator
//step 3.and since the second yield push a promise as well, we just return
//it here to the next promise flow
return it.next(session).value;
})
.then(function(user) {
//finall we get user info, and pass it back to the generator
//to make it printable inside generator
it.next(user);
})
.catch(it.throw.bind(it));//trigger an error to be caught inside the generator
//while error occurs in this promise flow