Hope this small and simple article will be a good half-technical (and half-theoretical) explanation of what cycle.js framework actually is and how it works, primarily for those who are just trying wrap their heads around it. And maybe this article will even be useful for those devs who have already been working with cycle for a while but were too lazy (or not curious enough) to get into the source code and understand what the framework core is actually doing for them internally. This article though won’t discover America for you, but may shed some extra light on the subject(s).
Picture, picture on the wall. You have probably seen this nice svg sketch on http://cycle.js.org/ visually explaining what cycle.js is all about: Your app and the external world as a circuit.
Hm… ok. So what is it all about? Right, about transforming inputs to outputs.
So main (upper rectangle on the picture) is just a function that transforms given input (sources) to some needed output (sinks). And actually it is a pure logical function — if you don’t understand what it is, you will get it later eventually (but probably not from this article), let’s put it:
sinks = main(sources)
or just: y = f(x) — resembles, right?
Ok, the post is not about high school mathematics. We all like pictures much more, so look at the drawing again. There is also the lower rectangle on the picture which holds bunch of things with side effects inside — those things are called drivers. Drivers — is a part of your application which connects your main with external world. And side effects are something that’s happening in external (to main function) world — drawing on a canvas, painting on a wall, sending requests, listening to events/birds, singing songs, shooting guns, making database queries — all those are made inside and by the drivers.
But what is interesting, drivers are just functions too, they are almost like our main function — except one thing — our main is (should be) pure and vestal and drivers are (can be) dirty and vicious — but for us now it doesn’t matter — they are still functions — and work basically similar way — (may) take something on input and (may) return some output. So presumably we may refer to drivers as an inverse function to main (look at the picture: main reflects in drivers like in a mirror). So, drivers take output of main — sinks, and return input for main — sources, let’s put it:
sources = drivers(sinks), and as we have seen before:
sinks = main(sources)
or:
sinks = main(drivers(sinks))
(those who see here functional equation should probably simplify their mindset a little bit for current moment)
What we’ve got here — is a chicken egg problem — this won’t work just so (at least) in JavaScript — JavaScript cares about declaration and reference order (you can not use variable before it was declared).
But what if I tell you that main purpose of cycle.js core is make this circular dependency between inputs and outputs — sources and sinks — just work? Yes, it is, quite simple, right?
Technically, this is done using proxy subject pattern which allows to emulate kind of circular dependency between observable streams. If you open cycle.js core source you will see about only one hundred (!) of LOCs (without comments). Cycle.js core is extremely small and exposes only one “run” function and which inside has six meaningful calls and return statement. I will explain this 7 (good number) lines.
Let’s go, code comes first, then explanation:
let sinkProxies = makeSinkProxies(drivers)
- This creates a proxy object for sinks (main outputs) — this is a temporary or intermediate “holder” for sinks. Inside it is done as it was said with help of subjects — which are things that can play role of observable (be observed and consumed) and observer (be consumer/subscriber for some other observable sources), then:
let sources = callDrivers(drivers, sinkProxies)
2. Then we create sources using supplied driver functions and just created sinkProxies, sources will give us an input streams for main. So we ready to call it:
let sinks = main(sources)
3) Well call main using sources get the “real” sinks as an output. Notice that calling main doesn’t mean that process of work has started, as observables (as people) are lazy by nature, they need subscribers (spectators) to start (and continue) working:
let subscription = replicateMany(sinks, sinkProxies).subscribe()
4) Then we link together sinks to sinkProxies (everything that happens inside sinks will be repeated inside sinkProxies created on step 1.) And we also subscribe to sinks — this will as a matter of fact launch our cycle.
Well, that’s all. Really. That is all what cycle.js core actually does — it creates and ensures dataflow circulation between sources (streams that come from drivers to main) and sinks (streams that come from main to drivers) by closing the loop. It is done with 4 (four) quite simple calls.
let sinksWithDispose = attachDisposeToSinks(sinks, subscription)
let sourcesWithDispose = attachDisposeToSources(sources)
return {sources: sourcesWithDispose, sinks: sinksWithDispose}
5) Finally, we attach dispose method to sources and sinks objects and return them as the result of run function.
Calling dispose on sources and sinks will destroy created loop by invoking dispose method on each drivers’s output and removing all subscriptions — and thus stop our cycle app, if there is no leaks in your app (due to wrong implementation details) you may be sure that after this everything is stopped and cleaned up by internal mechanics of stream libraries. This step may be needed for testing and hot reloading scenarios and some other cases where you need to re-instantiate cycles during your runtime environment lifecycle.
You may look closer at the source code for implementation details.
Wrapping up
So as you see cycle.js is very small in its core and extremely simple in its concept. But its brilliance and power come from this simplicity. Of course technical power comes from underlying observables/streams libraries like rxjs or mostjs, and eventually from drivers that do what all programs we write are about — i/o or side effects. What cycle.js brings to us — is very clear, structured and natural pattern of using observable streams in JavaScript for solving real problems.
And I’m pretty sure as we will see later those problems do not necessarily refer to “DOM”, “HTTP” or other font-end or even web related stuff — cycle.js potentially goes beyond those introducing more general purpose architectural pattern— but it is not the matter of current article. The task was just to show you internals of cycle.js core, technical reasoning and intent behind it. Understanding of this should probably make much more confident user of the framework.
Soon (April 2016) there will be released a new version of cycle.js written in TypeScript and generalized for use with diverse observable streams libraries, code will be changed quite a bit, but the concept will stay the same.
If you are new to cycle.js and observables you probably didn’t understand some details in this article, so I recommend you to follow this nice video courses by André Staltz, creator of cycle.js:
Introduction to Reactive Programming
RxJS Beyond the Basics: Creating Observables from scratch
Continue to explore cycle.js capabilities and return to this article later if you feel need in understanding what is it all about. Happy codding with cycle!