It started with a desire to play around with function decorators and the concept of generators. It ended up opening into something that I think is quite a bit greater
What do I mean?
Observation:
You can talk about a 1s throttle like this:
create a wrapped function
do
wait until function is invoked
call the function
wait until 1sec expires
loop
A 1s debounce can be be written as follows
create a wrapped function
do
wait until function is called
wait until either 1sec expires or function was called again
if 1sec expired then invoke function
loop
So what do generators have to do with this?
Well lets rehash about how a generator works.
You declare a javascript generator like so:
function * countLadder(seed) {
const startAt = 5 * seed
yield 'up'
for(let i = 0; i < 4; i++)
yield i + startAt
yield 'down'
for(let i = 3; i >= 0; i--)
yield i + startAt
}
for(let val of countLadder(3))
console.log(val) //up 15 16 17 18 down 18 17 16 15
it can also be called manually (as opposed to a foreach):
const iterator = countLadder(seed)
let result = iterator.next()
while( !result.done ) {
console.log(result.value) //up 15 16 17 18 down 18 17 16 15
result = iterator.next()
}
So you can see that effectively what is happening is that invoking a generator returns an object. that object has a next
method, and every time we call that method it runs until it hits the following yield. Anyone familiar with the enumerator pattern will note a similarity - that is exactly what this is.
The trully interesting thing here is what you can do if you yield back objects that take callbacks from your generator. Objects like promises. For example what if we did the following?
const runNextAsync = (iterator, nextVal) => {
const {done, value: promise} = iterator.next( nextVal )
if( done ) return
promise.then( val => runNextAsync(iterator, val) )
}
function * showRelatedProducts(currentItem) {
const relatedItemIds = yield $.get(`/item/${currentItem}/related`)
const top3Related = relatedItemIds.slice(0, 3)
const gettingTop3Items = top3Related.map(id => $.get(`/item/${id}`))
const relatedItems = yield Promise.all(gettingTop3Items)
for(let item of relatedItems)
appendToDom( renderItemTemplate(item) )
}
runNextAsync( showRelatedProducts(123) )
This is pretty much what libraries like co and bluebird do. It's also what hte upcoming await keyword does and the reason its kinda dumb.
Another interesting observation is that all sorts of things can be represented as promises. For example a setTimeout can be a promise
const timeoutPromise = ms => new Promise(resolve => setTimeout(resolve, ms))
timeoutPromise(1000).then(() => console.log("1 second has passed"))
In fact dom events can be represented as a sequence of promises with each promise representing the next time the event is triggered. So a function that returns promises.
const event2promise = (dom, eventName) => new Promise(resolve => dom.addEventListener(eventName, resolve))
runNextAsync((function * () {
const btn = document.querySelector ('.my-button')
while (true) {
yield event2promise(btn, 'click')
console.log ("The button was clicked")
}
})())
You might notice that this is basically an event stream and tehrefore starting to really seem like functional reactive programming territory. This is where gimgen comes in