Last active
August 29, 2015 14:22
-
-
Save jlongster/7740f3b4b2885febb2ad to your computer and use it in GitHub Desktop.
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
// Some of this is just preference, as you can do it in stuff like Rx | |
// as well but I find that this style scales better for readable code. | |
// Channels only provide a few operations like `take` (`<!` in CLJS) | |
// and don't care how your event loop is run. You manually create | |
// loops, as in `go-loop` in ClojureScript: | |
(go-loop [last-x 0] | |
(let [x (<! x-chan)] | |
(if (not (= x last-x)) | |
(recur x)))) | |
// This will continually read from `x-chan`, and stop when 2 unique | |
// values come through. It stops by simply not calling `recur`. | |
// What's nice about this (and different from everything else) is that | |
// this code is extremely readable: you immediately see how events | |
// come down the pipe, and how it stops. | |
// A side effect is that because we have a local loop, we can manage | |
// state locally across events. Here we have a local variable `x`. | |
// We don't have `go-loop` in JS, so it's not quite as nice: | |
go(function*() { | |
let lastX = 0; | |
while(true) { | |
let x = yield x-chan; | |
if(x === lastX) { | |
break; | |
} | |
lastX = x; | |
} | |
}); | |
// However, we could probably could build `goLoop` that removes some | |
// of that boilerplate, getting close to ClojureScript: | |
goLoop(function*(recur, lastX) { | |
let x = yield x-chan; | |
if(x !== lastX) { | |
recur(x); | |
} | |
}, 0); | |
// The nice thing in general is that I can do all sorts of interesting | |
// things and have complete control when to pump the event loop. This | |
// is a construct that gives me the ability to delay an action after | |
// `ms` of inactivity: | |
function signalAfterInactivity(ms) { | |
let inChan = chan(); | |
let outChan = chan(); | |
goLoop(function*(recur) { | |
// Wait for the first event, which will kick off the process of | |
// waiting for inactivity | |
yield inChan; | |
// Start another loop which determines when no signals have fired | |
// after `ms` | |
while(true) { | |
let [e, src] = yield alts([inChan, timeout(ms)]); | |
if(src !== inChan) { | |
// No activity has happened after `ms`, send a message to the | |
// user | |
yield put(outChan, true); | |
break; | |
} | |
// A signal came from `inChan`, loop again | |
}; | |
// Keep going for next time | |
recur(); | |
}); | |
return { in: inChan, out: outChan }; | |
}; | |
// Here is example usage, though it's not a very good example, the | |
// bigger point is how the above function was constructed. | |
var hideMenuChans = signalAfterInactivity(500); | |
go(function*() { | |
// Wait for the menu to open | |
yield listen('#open-menu', 'click'); | |
openMenu(); | |
// And then hide it, but this won't happen until a period of | |
// inactivity | |
yield put(hideMenuChans.in, true); | |
}); | |
// Start a process that closes the menu after a period of inactivity | |
goLoop(function*(recur) { | |
yield hideMenuChans.out; | |
closeMenu(); | |
recur(); | |
}); | |
// I simply find it much easier to reason about stream-like behavior | |
// with channels, and the code seems a lot easier to glance through. | |
// You could do a lot of this with other abstractions like Rx, but I | |
// think they start to unwieldy in more complex examples. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment