On Github leftstick / generator-fundamentals
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 wikipediaThe 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:
Run Code'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
Run Code'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 can:
Run Code'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 }
Syntax
gen.next(value)
Parameters
The value to send to the generator.Example
Run Code'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
Run Code'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.Run Code'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
, let's try controlling an async task with 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
Run Code'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