Skip to content

Instantly share code, notes, and snippets.

@nobitagit
Last active March 11, 2017 13:37
Show Gist options
  • Save nobitagit/70b25693b2311315d8236338d1bfdade to your computer and use it in GitHub Desktop.
Save nobitagit/70b25693b2311315d8236338d1bfdade to your computer and use it in GitHub Desktop.
Introduction to JavaScript Generators
// Intro to generators in JavaScript
// You can use: https://repl.it/languages/babel
// to follow along the code
// A generic counter
function counter() {
console.log('counter #1');
console.log('1');
console.log('2');
}
counter()
// logs:
// 1
// 2
// A counter as a generator
function* counter2() {
console.log('counter #2');
console.log('1');
console.log('2');
}
counter2();
// logs nothing
// Reason: a generator function returns an INSTANCE of itself when it is invoked
// Hence we should ideally call this function createCounter,
// as when invoked it just instantiates itself without actually executing anything.
function* createCounter() {
console.log('counter #3');
console.log('1');
console.log('2');
}
const counter3 = createCounter();
counter3.next();
// logs:
// 1
// 2
// If we want to pause a generator we use the keyword `yield`
function* createCounterWithYeld() {
console.log('counter #4');
console.log('1');
yield;
console.log('2');
}
const counter4 = createCounterWithYeld();
counter4.next();
// logs:
// 1
console.log('invoke next on counter #4');
counter4.next();
// logs:
// 2
// We can return values from generators
function* createCounter() {
yield 'Instantiated';
}
const counter = createCounter();
console.log('1nd call to counter.next()');
console.log(counter.next());
// logs:
// { value: 'Instantiated', done: false }
// Done is false because we haven't gone past the `yield`
// Executing `next` again will mark the function as `done`
console.log();
console.log('2nd call to counter.next()');
console.log(counter.next());
console.log();
// logs:
// { value: 'Instantiated', done: false }
// Note that "value": undefined this time
// We can supply values to the function and have them assigned to variables
console.log('createCounterWithValues');
function* createCounterWithValues() {
const start = yield;
console.log(start);
}
const counter2 = createCounterWithValues();
console.log(counter2.next());
console.log(counter2.next('5'));
// logs:
// { value: undefined, done: false }
// 5
// { value: undefined, done: true }
// While the code in a generator looks synchronous it is indeed
// fully async thanks to `yield` and `next`.
function* createUser() {
const uId = yield;
console.log(`User Id: ${uId}`);
const companyDetails = yield;
console.log(`Company: ${companyDetails.name}`);
}
const user = createUser();
user.next();
console.log(user.next('abcd-efg-12-34-45'));
// Request company details from a server, once it is done
// pass it to the generator and resume execution
console.log(user.next({name: 'Facebook', nation: 'USA'}));
// Lets' suppose, though that we execute the first part of the generator, then we do some
// other task and we want to stop the generator a throw an error
const user2 = createUser();
user2.next();
console.log(user.next('hilm-efg-34-45-88'));
// Request company details returns a 4xx error, so we want to actually throw an error.
// We use the `throw` method
// This way though will hang the app or crash it badly as we see if we
// uncomment the below line to test this function
// user2.throw();
console.log('throw call commented here, uncomment to test it');
console.log();
// How to solve this then?
// Since generators' code looks synchronous we can treat it like a normal sync call
// and use a standard try/catch block
function* createUserWithTryCatch() {
const uId = yield;
console.log(`User Id: ${uId}`);
try {
const companyDetails = yield;
console.log(`Company: ${companyDetails.name}`);
} catch(e) {
console.log('An error was caught!!');
}
}
const user3 = createUserWithTryCatch();
user3.next();
user.next('hilm-efg-34-45-88');
user3.next();
user3.throw();
console.log('The app does not hang or break, hence this code runs');
// If we want to pass an error to the function we just pass it as a paramenter
// to the throw function, just like: user3.throw('Something has gone wrong');
// and the error will be available inside the catch block
function* daysOfTheWeek() {
yield 'Monday';
yield 'Tuesday';
yield 'Wednesday';
yield 'Thursday';
yield 'Friday';
yield 'Saturday';
yield 'Sunday';
}
const day = daysOfTheWeek();
console.log(day.next());
console.log(day.next());
console.log(day.next());
console.log(day.next());
console.log(day.next());
console.log(day.next());
console.log(day.next());
console.log(day.next());
// logs:
// { value: 'Monday', done: false }
// { value: 'Tuesday', done: false }
// { value: 'Wednesday', done: false }
// { value: 'Thursday', done: false }
// { value: 'Friday', done: false }
// { value: 'Saturday', done: false }
// { value: 'Sunday', done: false }
// { value: undefined, done: true }
// A cleaner way to go through all of the yields in one go
// is to use a for...of loop like so:
const day2 = daysOfTheWeek();
for (let dayName of day2) {
console.log(dayName);
}
// logs:
// Monday
// Tuesday
// Wednesday
// Thursday
// Friday
// Saturday
// Sunday
// Note how the for...of executes the next function and returns the value
// of the function call.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment