Skip to content

Instantly share code, notes, and snippets.

@lijunle
Last active November 6, 2017 06:49
Show Gist options
  • Save lijunle/2e2c45ba1a9d08a44b8a3f94640a8287 to your computer and use it in GitHub Desktop.
Save lijunle/2e2c45ba1a9d08a44b8a3f94640a8287 to your computer and use it in GitHub Desktop.
Generator/yield introduction
import performanceNow = require('performance-now');
console.log();
console.log('# What is yield?');
console.log();
console.log();
console.log('## Return Array');
console.log();
function returnArray(): number[] {
return [1, 2, 3];
}
function returnArrayRunner(): void {
for (const i of returnArray()) {
console.log(i);
}
}
// returnArrayRunner();
console.log();
console.log('## Yield Array');
console.log();
function* yieldArray(): IterableIterator<number> {
yield 1;
yield 2;
yield 3;
}
function yieldArrayRunner(): void {
const yieldIterator: IterableIterator<number> = yieldArray();
let current: IteratorResult<number> = yieldIterator.next();
while (!current.done) {
console.log(current.value);
current = yieldIterator.next();
};
}
// yieldArrayRunner();
console.log();
console.log('# There are more!');
console.log();
console.log();
console.log('## Yield With Next Value');
console.log();
function* squareThreeSum(): IterableIterator<number> {
const squareOne = yield 1;
console.log(`1^2 = ${squareOne}`);
const squareTwo = squareOne + (yield 2);
console.log(`1^2 + 2^2 = ${squareTwo}`);
const squareThree = squareTwo + (yield 3);
console.log(`1^2 + 2^2 + 3^2 = ${squareThree}`);
}
function squareThreeSumRunner(): void {
const squareSumIterator: IterableIterator<number> = squareThreeSum();
let current: IteratorResult<number> = squareSumIterator.next();
while (!current.done) {
const valueSquare: number = current.value ** 2;
current = squareSumIterator.next(valueSquare);
}
}
// squareThreeSumRunner();
console.log();
console.log('## Yield With Return Value');
console.log();
function* squareSum(n: number): IterableIterator<number> {
let sum: number = 0;
while (n) {
sum += yield n;
n--;
}
return sum;
}
function squareSumRunner(n: number): number {
const squareSumIterator: IterableIterator<number> = squareSum(n);
let current: IteratorResult<number> = squareSumIterator.next();
while (!current.done) {
const valueSquare: number = current.value ** 2;
current = squareSumIterator.next(valueSquare);
}
return current.value;
}
// console.log(`1^2 = ${squareSumRunner(1)}`);
// console.log(`1^2 + 2^2 = ${squareSumRunner(2)}`);
// console.log(`1^2 + 2^2 + 3^2 = ${squareSumRunner(3)}`);
console.log();
console.log('## Yield With Try-Catch');
console.log();
function* squareSumWithTryCatch(n: number): IterableIterator<number> {
try {
let sum: number = 0;
while (n) {
sum += yield n;
n--;
}
return sum;
} catch (error) {
console.log(error);
}
}
function squareSumWithTryCatchRunner(n: number): number {
const squareSumIterator: IterableIterator<number> = squareSumWithTryCatch(n);
let current: IteratorResult<number> = squareSumIterator.next();
while (!current.done) {
const valueSquare: number = current.value ** 2;
if (valueSquare <= 999) {
current = squareSumIterator.next(valueSquare);
} else {
squareSumIterator.throw!(new RangeError(`We don't have resource to calculate squareSum for ${n}`));
}
}
return current.value;
}
// console.log(`1^2 + 2^2 + 3^2 = ${squareSumWithTryCatchRunner(3)}`);
// try {
// console.log(`1^2 + 2^2 + 3^2 + ... + 100^2 = ${squareSumWithTryCatchRunner(100)}`);
// } catch (e) {
// // When the error is handled inside the generator, it should not rethrow again.
// // Not sure if this is a bug of TypeScript, check V8 implementation for this.
// }
console.log();
console.log('## Yield Another Generator');
console.log();
function* inner(): IterableIterator<number> {
yield 1;
yield 2;
yield 3;
}
function* outer(): IterableIterator<number> {
yield 0;
// From MDN, we should NOT need to convert generator to array. It looks like a bug of TypeScript.
// See this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield*
yield* Array.from(inner());
yield 4;
yield 5;
}
function nestedYieldRunner(): void {
const outerIterator: IterableIterator<number> = outer();
let current: IteratorResult<number> = outerIterator.next();
while (!current.done) {
console.log(current.value);
current = outerIterator.next();
}
}
// nestedYieldRunner();
console.log();
console.log('# Do you remember async/await?')
console.log();
function fetch(): Promise<string> {
return Promise.resolve('Something');
}
function validate(s: string): Promise<boolean> {
return Promise.resolve(s.length > 0);
}
async function requestAsync(): Promise<void> {
const result: string = await fetch();
const validated: boolean = await validate(result);
console.log(`${result} is validated: ${validated}`);
}
function* requestYield(): IterableIterator<Promise<any>> {
const result: string = yield fetch();
const validated: boolean = yield validate(result);
console.log(`${result} is validated: ${validated}`);
}
function requestRunner(): void {
requestAsync().then(() => console.log('Async/await is done!'));
// Checkout `co` NPM package, which did convert the generator to Promise flow. https://www.npmjs.com/package/co
function runGenerator(currentIterator: IteratorResult<Promise<any>>): Promise<any> {
return currentIterator.value.then((result) => {
const nextIterator: IteratorResult<Promise<any>> = requestIterator.next(result);
if (!nextIterator.done) {
return runGenerator(nextIterator);
} else {
return nextIterator.value;
}
});
}
const requestIterator: Iterator<Promise<any>> = requestYield();
runGenerator(requestIterator.next()).then(() => console.log('Yield is done!'));
}
// requestRunner();
console.log();
console.log('# Control Flow!')
console.log();
console.log();
console.log('## Check and continue')
console.log();
function getSafe(obj: any): any {
const a = obj.a;
if (!a) {
throw new Error(`There is no object.a`);
}
const b = a.b;
if (!b) {
throw new Error(`There is no a.b`);
}
const c = b.c;
if (!c) {
throw new Error(`There is no b.c`);
}
return c;
}
function getSafeRunner(obj: any): void {
try {
const result = getSafe(obj);
console.log(result);
} catch (error) {
console.log(error.message);
}
}
// getSafeRunner({});
// getSafeRunner({a:{}});
// getSafeRunner({a:{b:{}}});
// getSafeRunner({a:{b:{c:1}}});
function* getSafeYield(obj: any): IterableIterator<any> {
const a = yield obj.a;
const b = yield a.b;
const c = yield b.c;
return c;
}
function getSafeYieldRunner(obj: any): void {
const getSafeIterator: IterableIterator<any> = getSafeYield(obj);
let current: IteratorResult<any> = getSafeIterator.next();
while (!current.done) {
const result = current.value;
if (!result) {
console.log(`The access get undefined, stop the access chain`);
break;
} else {
current = getSafeIterator.next(result);
if (current.done) {
console.log(current.value);
}
}
}
}
// getSafeYieldRunner({});
// getSafeYieldRunner({a:{}});
// getSafeYieldRunner({a:{b:{}}});
// getSafeYieldRunner({a:{b:{c:1}}});
console.log();
console.log('## Pause for next frame')
console.log();
function canFit(text: string, dom: any): boolean {
// The logic to check if the text is fit inside the DOM.
// In real case, it does something very heavily.
// For demo, we eat some CPU and returns true;
let sum = 0;
for (let i = 0; i < 99999; i++) {
sum += i;
}
return sum > 0;
}
function* binarySearch(text: string, dom: any): IterableIterator<string> {
let left = 1;
let right = text.length;
while (left < right) {
const middle = (left + right) / 2;
const subText = text.substr(0, middle);
if (canFit(subText, dom)) {
left = middle;
} else {
right = middle;
}
// The runner can leverage this yield to control if we should continue NOW, or continue on NEXT FRAME.
yield;
}
return text.substr(0, left);
}
function requestAnimationFrame(callback: () => void) {
// Mock the `requestAnimationFrame` method in node.js environment.
setTimeout(callback);
}
function binarySearchRunner(): void {
const text: string = Array.from({ length: 9999 }).map(() => 'x').join('');
const binarySearchIterator: IterableIterator<string> = binarySearch(text, {});
let current: IteratorResult<string> = binarySearchIterator.next();
function runBinarySearch(): void {
// Use `performance.now` in browser.
const startTime: number = performanceNow();
// We should use `15ms` instead of `1ms` here. My node.js environment is calculating too fast.
while (!current.done && performanceNow() - startTime < 1) {
// console.log(performanceNow());
current = binarySearchIterator.next();
}
if (current.done) {
// The generator has completed, the `current` iterator result has the final result.
console.log(`Binary search finished. Get result length: ${current.value.length}`);
} else {
// We have run out of time on the current frame. Pause now, then run on the next frame.
console.log(`Pause binary search and wait for next frame. Run [${startTime} - ${performanceNow()}]`);
requestAnimationFrame(runBinarySearch);
}
}
runBinarySearch();
}
// binarySearchRunner();
console.log();
console.log('## Task-based control flow');
console.log();
console.log();
console.log('## Turn continuation-passing style (aka, CPS) to generator');
console.log();
console.log();
console.log('## Wrap generator as implementation details');
console.log();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment