Skip to content

Instantly share code, notes, and snippets.

@jlongster
Last active August 29, 2015 14:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jlongster/7740f3b4b2885febb2ad to your computer and use it in GitHub Desktop.
Save jlongster/7740f3b4b2885febb2ad to your computer and use it in GitHub Desktop.
// 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