title | date | tags |
---|---|---|
Generators in JS |
2014-08-12 11:10:08 +0200 |
generators, js, async |
Generators are function executions that can be suspended and resumed.
It's supported in Firefox and node 0.11 with --harmony, unclear when it gets to stable. You can also transpile using traceur or regenerator.
Keywords are function*
, yield
and yield*
. API is: {value: <value>, done: <bool>} <generator>.next(value)
.
Example generator function:
function* channel() {
console.log('running channel func');
var name = yield 'hello what is your name';
return 'well hi ' + name;
}
Calling channel will return a generator, and not execute the function body. Calling .next
will execute the function body until the first yield or return statement. The return value is an object that looks like {value: <value>, done: <bool>}
. When calling next you can pass an argument that will then be the (direct) return value of the yield in the generator itself.
gen = channel();
gen.next(); // {value: 'hello what is your name', done: false}
gen.next('Marcel'); // {value: 'well hi Marcel', done: true}
Instead of calling .next
you can also loop through the values of the generator:
function* iter () {
for (var i = 0; i < 10; i++) yield i;
}
for (var val of iter()) {
// val is 0 to 9
}
Note that we use for-of instead for-in, because we want to loop over the values and not over the keys.
Speaking of which, strangely enough we can't iterate over the keys of a generator.
for (val in wrappedIter()) {
console.log(val); // doesn not get called
}
console.log(wrappedIter().next)); // does work
A function can yield another generator function using the yield*
keyword. In a way it's about flattening nested generators.
var val;
function* iter () {
for (var i = 0; i < 3; i++) yield i;
}
function* wrappedIter () {
yield* iter(); // yield all yields of this generator
yield 'done';
}
for (val of wrappedIter()) {
console.log(val); // 0, 1, 2, 'done'
}
You can throw errors using .throw
.
Demonstration of basic generator and next:
function* channel() {
console.log('running channel func');
var name = yield 'hello what is your name';
return 'well hi ' + name;
}
var gen;
var iter;
// Get generator. The function body did not get executed yet.
gen = channel();
// Run the function body until the first yield or end of function
iter = gen.next();
console.log(iter.value); // hello what is your name
// Check if we are done (we are not)
console.log('we are', iter.done ? '' : 'not', 'done');
if (!iter.done) {
iter = gen.next('Marcel');
console.log(iter.value); // well hi Marcel
}
Generators are used in combination with thunks, promises and libraries such as co. Also see the promises, thunks, generators and async in js article.