Skip to content

Instantly share code, notes, and snippets.

@togakangaroo
Last active September 8, 2016 17:46
Show Gist options
  • Save togakangaroo/65cbbb686b6fafdf68706c8706264f60 to your computer and use it in GitHub Desktop.
Save togakangaroo/65cbbb686b6fafdf68706c8706264f60 to your computer and use it in GitHub Desktop.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment