Last active
November 6, 2017 06:49
-
-
Save lijunle/2e2c45ba1a9d08a44b8a3f94640a8287 to your computer and use it in GitHub Desktop.
Generator/yield introduction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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