]>
Commit | Line | Data |
---|---|---|
1 | --- | |
2 | layout: post | |
3 | title: "Fluorine: the library to get things flowing" | |
4 | tags: english javascript | |
5 | color: cyan | |
6 | header_image: fluorine.gif | |
7 | description: "Fluorine is a flow based library that makes complex | |
8 | async operations really simple." | |
9 | --- | |
10 | ||
11 | Today I was happily surprised with the fact that [fluorine ][fluorine] | |
12 | has been made publicly available with an MIT License! | |
13 | This is a library created by [azendal][azendal] | |
14 | as a way to solve some of the more complex flows in [breezi][breezi]. | |
15 | The problem had been bugging us for a while and the several existing | |
16 | solutions to the async problem didn't feel right for it, and then | |
17 | fluorine arrived to the [neon][neon] [family][cobalt] [of][thulium] | |
18 | [libraries][tellurium]. | |
19 | ||
20 | Fluorine is a really simple flow abstraction for javascript that doesn't | |
21 | do much: You can have a *Flow*, which has a series *Steps*; a step has a | |
22 | name, a list of dependencies, and a fulfill function. The way it works | |
23 | is that flows run automatically, and every step that can be taken *is | |
24 | taken*, then its dependents are executed, and so on until all nodes are | |
25 | fulfilled. That's all there is to it, and that's all it needs to provide | |
26 | powerful asynchronous constructs that can stay decoupled. | |
27 | ||
28 | But let's code a simple example: | |
29 | ||
30 | In the following example we're loading an application where we allow our | |
31 | user to view a 3D model of their favorite 1985 vintage action figures in | |
32 | a fake shelf. We *show a loading screen* while we *get the data for the | |
33 | models*, then *fetch the models themselves*, also *request the user data* | |
34 | (so we can see their favorites), plus *all the latest posts* from the most | |
35 | popular social network for enthusiasts of 1985 vintage action figures | |
36 | — G.I. Yo. Once we have the model and user data, we can *display the shelf* | |
37 | so the user can see the name of 1985 snowman and finally settle that argument | |
38 | with joe about which 1985 vintage action figure is the best 1985 vintage | |
39 | action figure, so we have to: *hide the loading scren*, show the models | |
40 | with a generic image and *leave a loader in the social network box*. | |
41 | Once the models are fetched, we can start *displaying them* in the shelf | |
42 | instead of the generic image while it loaded; once we have the social | |
43 | networks, we *put the posts in there*. So, that's easy right?, no chance | |
44 | of turning *this* into a mess... | |
45 | ||
46 | ![A boring old diagram][fig1] | |
47 | *34 indentations later...* | |
48 | ||
49 | I put this in a diagram with handy color coded arrows so we don't get | |
50 | lost. Now we know what operations depend on each other, and that's all we | |
51 | need for fluorine. So, we can now turn that diagram into a flow: | |
52 | ||
53 | <pre class="language-javascript"><code class="language-javascript"> | |
54 | var giFlow = new Flow(); | |
55 | ||
56 | // Top Row Nodes (as shown in the diagram) | |
57 | ||
58 | giFlow.step('showLoadingScreen')(function (step) { | |
59 | loadingScreen.show(); | |
60 | step.success(); | |
61 | }); | |
62 | ||
63 | giFlow.step('fetchUserData')(function (step) { | |
64 | UserData.fetch(function (userData) { | |
65 | step.success(userData); | |
66 | }); | |
67 | }); | |
68 | ||
69 | giFlow.step('fetchModelData')(function (step) { | |
70 | ModelData.fetch(function (modelData) { | |
71 | step.success(modelData); | |
72 | }); | |
73 | }); | |
74 | ||
75 | giFlow.step('fetchSocialData')(function (step) { | |
76 | SocialData.fetch(function (socialData) { | |
77 | step.success(socialData); | |
78 | }); | |
79 | }); | |
80 | ||
81 | // Middle Row Nodes (as shown in the diagram) | |
82 | ||
83 | giFlow.step('hideLoadingScreen').dependsOn('showLoadingScreen', 'fetchUserData', 'fetchModelData')(function (step) { | |
84 | loadingScreen.hide(); | |
85 | step.success(); | |
86 | }); | |
87 | ||
88 | giFlow.step('displayPlaceholderSocialBox').dependsOn('fetchUserData', 'fetchModelData')(function (step) { | |
89 | socialBox.display(function () { | |
90 | step.success(); | |
91 | }); | |
92 | }); | |
93 | ||
94 | giFlow.step('displayModelShelf').dependsOn('fetchUserData', 'fetchModelData')(function (step) { | |
95 | modelShelf.display(step.data.fetchUserData, step.data.fetchModelData, function () { | |
96 | step.success(); | |
97 | }); | |
98 | }); | |
99 | ||
100 | giFlow.step('fetchModels').dependsOn('fetchModelData')(function (step) { | |
101 | Model.fetch(step.data.fetchModelData, function () { | |
102 | step.success(); | |
103 | }); | |
104 | }); | |
105 | ||
106 | // Bottom Row Nodes (as shown in the diagram) | |
107 | ||
108 | giFlow.step('fillInGIYoPosts').dependsOn('fetchSocialData', 'displayPlaceholderSocialBox')(function (step) { | |
109 | socialBox.fillInGIYOPosts(step.data.fetchSocialData, function () { | |
110 | step.success(); | |
111 | }); | |
112 | }); | |
113 | ||
114 | giFlow.step('replaceModels').dependsOn('fetchModels', 'displayModelShelf')(function (step) { | |
115 | modelShelf.replaceModels(step.data.fetchModels, function () { | |
116 | step.success(); | |
117 | }); | |
118 | }); | |
119 | </code></pre> | |
120 | ||
121 | That's all there is to fluorine: you define steps, you fulfill them. | |
122 | Every step may have dependents, and it can access their data from the | |
123 | callback! Now you can define complex operations step by step, you could | |
124 | create complex branching by not triggering a step's success method. | |
125 | Another important part of fluorine is that modifying flows is really | |
126 | easy, as long as you have clearly defined nodes, you can just move | |
127 | around dependencies to alter the way the program runs! | |
128 | You can even check your work against the spec by converting it | |
129 | to dot format (eg. `giFlow.toDot()`): | |
130 | ||
131 | digraph { | |
132 | showLoadingScreen | |
133 | fetchUserData | |
134 | fetchModelData | |
135 | fetchSocialData | |
136 | hideLoadingScreen | |
137 | showLoadingScreen -> hideLoadingScreen | |
138 | fetchUserData -> hideLoadingScreen | |
139 | fetchModelData -> hideLoadingScreen | |
140 | displayPlaceholderSocialBox | |
141 | fetchUserData -> displayPlaceholderSocialBox | |
142 | fetchModelData -> displayPlaceholderSocialBox | |
143 | displayModelShelf | |
144 | fetchUserData -> displayModelShelf | |
145 | fetchModelData -> displayModelShelf | |
146 | fetchModels | |
147 | fetchModelData -> fetchModels | |
148 | fillInGIYoPosts | |
149 | fetchSocialData -> fillInGIYoPosts | |
150 | displayPlaceholderSocialBox -> fillInGIYoPosts | |
151 | replaceModels | |
152 | fetchModels -> replaceModels | |
153 | displayModelShelf -> replaceModels | |
154 | } | |
155 | ||
156 | dot -Tpng that_file_up_there.dot -o fancy_graphic.png | |
157 | ||
158 | ![Fancy Graphic][fig2] | |
159 | ||
160 | They look the same (ish)! It even did a better job at putting it together! | |
161 | ||
162 | So that's fluorine, a pretty simple flow tool that can let you build | |
163 | complex stuff, you could implement a neural network, or a behavior tree | |
164 | for a game actor, or a loop of events, or do some complex distributed | |
165 | tasks, you can use it for almost anything as long as you can graph it, | |
166 | because that's all it really is: graphs. You could even create dynamic | |
167 | nodes and have a flow that is created programatically! | |
168 | ||
169 | I hope that example was enough to get you interested on how you can use | |
170 | it now that it's open to everyone! Have fun :D | |
171 | ||
172 | [fluorine]: https://github.com/freshout-dev/fluorine | |
173 | [azendal]: https://github.com/azendal | |
174 | [breezi]: https://breezi.com | |
175 | [neon]: https://azendal.github.io/neon | |
176 | [cobalt]: https://github.com/rbdr/cobalt | |
177 | [thulium]: https://freshout-dev.github.io/thulium | |
178 | [tellurium]: https://github.com/azendal/tellurium | |
179 | [fig1]: https://docs.google.com/drawings/d/1a3xVTO0qfSqt_fKWuKzeQFGbtOvQbfVV63_nGD5xcq0/pub?w=960&h=720 | |
180 | [fig2]: http://ns.vc/nXBk.png |