Last active
June 21, 2016 08:53
-
-
Save rjvdw/4f8502e8e7bce7f8209002556ce8d2a7 to your computer and use it in GitHub Desktop.
This gist demonstrates how generator functions, iterators and promises work in JavaScript.
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
/* # Fun With Iterators | |
* | |
* This snippet of code demonstrates how generator functions, iterators | |
* and promises work in JavaScript. | |
* | |
* ## Promises | |
* | |
* A promise is an object that represents a value that is yet to be | |
* determined. As soon as this value is known the promise will be | |
* resolved. If however something went wrong, the promise will be | |
* rejected. | |
* | |
* See [1] for more information. | |
* | |
* ## Iterators | |
* | |
* An iterator is an object that can be used to produce a sequence of | |
* values. | |
* | |
* See [2] for more information. | |
* | |
* ## Generator functions | |
* | |
* A generator function is a function which returns a Generator object. | |
* Generator objects act like iterators. Generator functions are defined | |
* using the `function*` declaration. | |
* | |
* Generator functions may use the keyword `yield` to return a value | |
* from the Generator. Alternatively the keyword `yield*` may be used to | |
* yield all values from another iterator. | |
* | |
* See [3] and [4] for more information. | |
* | |
* | |
* [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise | |
* [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | |
* [3]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* | |
* [4]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator | |
*/ | |
'use strict' | |
const SECONDS_TO_COUNTDOWN = 10 | |
const FLIGHT_DURATION = 30 | |
const CHANCE_TO_EXPLODE_DURING_COUNTDOWN = 0.01 | |
const CHANCE_TO_EXPLODE_DURING_LIFTOFF = 0.1 | |
coroutine(function* simulateRocketLiftoff(secondsToCountDown) { | |
try { | |
yield* countdown(secondsToCountDown) | |
yield liftoff() | |
console.log('Success!') | |
} | |
catch (err) { | |
console.error(err.message) | |
} | |
})(SECONDS_TO_COUNTDOWN) | |
/** | |
* Turns a generator function into a coroutine. | |
* | |
* The generator may yield promises. Control will be returned to the | |
* generator as soon as the promise is resolved or rejected. | |
* | |
* @link https://en.wikipedia.org/wiki/Coroutine | |
* @param {generator} gen The generator function to turn into a | |
* coroutine. | |
* @return {Function} Starts the coroutine. Arguments passed to this | |
* function will be passed along to the generator. | |
*/ | |
function coroutine(gen) { | |
return function runner() { | |
const iter = gen.apply(null, arguments) | |
return loop(iter) | |
} | |
/** | |
* Loops through the iterator that was generated by the generator. | |
* | |
* @param {Iterator} iter Iterator generated by the generator. | |
* @param {*} [prev] The value returned by the last iteration. | |
* @return {Promise} A promise that resolves when iteration is done. | |
*/ | |
function loop(iter, prev) { | |
return handleIteration(iter, iter.next(prev)) | |
} | |
/** | |
* Handles a single iteration. | |
* | |
* @param {Iterator} iter Iterator generated by the | |
* Generator. | |
* @param {IteratorResult} iteration An iteration result. | |
* @return {Promise} A promise that resolves when iteration is done. | |
*/ | |
function handleIteration(iter, iteration) { | |
if (iteration.done) return iteration.value | |
/* | |
* If the promise resolves, continue iterating. If it does not, | |
* throw the error, so that it may be handled from within the | |
* coroutine. | |
*/ | |
return iteration.value | |
.then(value => loop(iter, value)) | |
.catch(err => handleIteration(iter, iter.throw(err))) | |
} | |
} | |
/** | |
* Simulates the count down before lift off. | |
* | |
* There is a chance that the rocket will explode during count down. | |
* | |
* @param {Number} from The number of seconds to count down. | |
* @throws {Error} Thrown if the rocket exploded during count down. | |
*/ | |
function* countdown(from) { | |
while (from > 0) { | |
console.log(`T minus ${from}`) | |
yield sleep(1) | |
if (Math.random() < CHANCE_TO_EXPLODE_DURING_COUNTDOWN) { | |
throw new Error('The rocket exploded during countdown!') | |
} | |
from -= 1 | |
} | |
} | |
/** | |
* Simulates the lift off. | |
* | |
* There is a chance that the rocket will explode during lift off. | |
* | |
* @return {Promise} A promise that will resolve if the lift off was | |
* successful, or reject if the rocket exploded. | |
*/ | |
function liftoff() { | |
console.log('Lift off!') | |
return sleep(FLIGHT_DURATION) | |
.then(() => new Promise((resolve, reject) => { | |
if (Math.random() < CHANCE_TO_EXPLODE_DURING_LIFTOFF) { | |
reject(new Error('The rocket exploded during liftoff!')) | |
} | |
else { | |
resolve() | |
} | |
})) | |
} | |
/** | |
* Sleeps for a given number of seconds. | |
* | |
* @param {Number} seconds The number of seconds to sleep. | |
* @return {Promise} A promise that will resolve after the specified | |
* number of seconds. | |
*/ | |
function sleep(seconds) { | |
return new Promise(resolve => setTimeout(resolve, 1000 * seconds)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment