barrcode

Firebase and Medium Published on 9/08/15 about javascript

I recently started sprinkling some CSP-style channels into a Firebase app. The streaming nature of Firebase data is a perfect fit for channels.

A simple example:


import { chan, go, put, take, sleep, repeat, repeatTake, CLOSED } from 'medium'
import Firebase from 'firebase'

var items = chan()

var fb = new Firebase('http://myfancyapp.firebaseio.com')

fb.child('items').on('child_added', (snap) => put(items, snap.val()))

repeatTake(items, someRenderFunction)

Now let's push a new item to Firebase every 2 seconds.


import { chan, go, put, take, sleep, repeat, repeatTake, CLOSED } from 'medium'
import Firebase from 'firebase'

var items = chan()

var fb = new Firebase('http://myfancyapp.firebaseio.com')

fb.child('items').on('child_added', (snap) => put(items, snap.val()))

repeatTake(items, renderUIForItem)

repeat(async () => {
  await sleep(2000)
  fb.child('items').push({ createdAt: Date.now() })
})

Great, though it would appear that this could go on indefinitely! We can limit it to 10 new items by using repeat's return value to populate the next iterations arguments. This helps simplify state-management inside repeat loops.

repeat(async (i=1) => {
  await sleep(2000)
  fb.child('items').push({ createdAt: Date.now() })
  return i === 10 ? CLOSED : i + 1
})

Now, let's say Firebase already had some existing items. Our renderUIForItem would end up getting called in a burst of initial activity, and our fancy item adding animations would suddenly look a bit jarring.

There are a few ways to do this, but let's do what needs the least explaining.

import { chan, go, put, take, sleep, repeat, repeatTake, CLOSED } from 'medium'
import Firebase from 'firebase'

var items = chan()
var ticks = chan()

var fb = new Firebase('http://myfancyapp.firebaseio.com')

fb.child('items').on('child_added', (snap) => put(items, snap.val()))

repeatTake(items, async (item) => {
  await take(ticks)
  renderUIForItem(item)
})

repeat(async (i=1) => {
  await sleep(2000)
  fb.child('items').push({ createdAt: Date.now() })
  return i === 10 ? CLOSED : i + 1
})

repeat(async () => {
  await sleep(300)
  await put(ticks, true)
})

Now our repeatTake on items will have to wait for a tick before continuing to render, which slows it down to at most 1 render per 300 ms.

There is a refreshing lack of assignments and state to support this coordination, and I think that is just the start of what can be gained by adopting channels as your primary async abstraction.

Check out Medium for the above CSP library.