Closed alidcast closed 7 years ago
H! Vuency looks like an interesting approach to managing discreet processes occurring over time. I do not care to know much about Ember on which the idea is based but was not impressed when I read on their FAQ that the solution is essentially an attempt to do what Observables already do, just not nearly as well: http://ember-concurrency.com/#/docs/faq
I love the simplicity and power of Vue for constructing the UI and have been thinking about the best way to make the function flow in a Vue VM dynamic, conditional and injectable at runtime (like templates already are). I require this because I use a FSM approach to controlling the app and have many tool features to manage. Vue is missing this Controller layer so there is something left to be done to fill the gap.
Looking into Vuency I also see that it requires a hardwired approach, where Tasks need to be bound under a task property before a component is created and then finally created via a factory. This is not as reactive as I would like my app and it's processes to be, a configuration softwired approach would make everything more flexible and allow for a more dynamic app.
Diving into RxJS lately I see immediate surface level parallels between Vuency and RxJS in a number of ways. However, RxJS follows the ES7 Observable spec (https://github.com/tc39/proposal-observable) and already has task building constructs in Observables, Observers, Schedulers, Subscriptions that can be canceled and restarted and comes with a ton of powerful Operators (modifiers) to create processing flows with.
Most importantly Observables as task engines take in events of all types from many sources, so everything is a stream and can be handled with the same RxJS (or add your favorite stream library here) task building construction kit.
The thing that attracts me to Vuency is just the idea of a Task. Here I've been developing what I call Services and looking at the best means to inject them into a barebones Vue VM. Looking at Vuency there is some reinvention of what RxJS offers. Presently, I use Observables and then Operators as their task modifiers, then subscribe to them with any Observer.
Essentially, it looks like Tasks wrap functions to provide them with a management environment. Observable pipelines and subscribed Observer/s already accomplish this. Anyways, these are my initial thoughts while trying hard to stay focused on as few dev tools as possible :)
Working through some further investigations on RxJS and Vue coupling, will keep thinking about Tasks now that I know more about what they are...
Thank you for the thorough and detailed comment. When I get a chance later this week, I will look into Observables so that I can better understand your points.
You are correct, the goal with tasks is to give you an extra layer of control between an event and the execution of the event.
Briefly, this is what Vuency does:
isRunning
for handling UI interactions.flow('restart', {delay: 400})
similar to a debounce but without messy timeouts task.abort()
or taskInstance.cancel()
.beforeStart()
or .onCanel()
to handle different results of a task as well as semantically separate code nth(1, { keepRunning: true })
to simulate a infinite looptimeout
which are cleared when the operation is over task.runOn()
The intended use for tasks is indeed under the tasks
property. This makes them easier to work with, as the task object can be injected into the component instance, similar to Vue's data
and method
properties. But you can also use them outside of it via the task factory function, e.g. myTask = task(function* (){})
and do with it what you like.
I do understand that theres some overlap with Observables. I looked into them before starting Vuency, but they seemed too bloated and difficult to understand (and saw similar opinions with users on Twitter). So I wanted to create something lightweight and easy to use, which I think tasks so far accomplish. It's just a question of what degree they are reinventing the wheel versus making developers more productive and coding more enjoyable like Vuejs does. :)
I'm going to look further into what Observables do best so that perhaps Vuency can incorporate them (and maybe not do itself) and make them easier to work with, but there are no guarantees. If you think of any concrete examples let me know.
Just chiming in some thoughts re @MVSICA-FICTA's thoughts:
I was/am a huge RxJS fan but it has many warts when you "cross the monad"; the transition that Observables make from monadic/declarative to alive/subscribed/stateful is often awkward with UI libraries. Even Cycle.js suffered from this: the moment you want to drive 2+ chunks of UI based on the same stream, you run the risk of multiply-subscribing to that observable unless you're careful to litter .publish().refCount()
all over the place, which is why xstreams is a thing.
It's also telling that so many of the examples you'll find in the wild for, say, typeahead search, end up resorting to doing mutation inside a .do(...)
operator because it's simply waaaay too complicated to do it the "pure" way.
I've found the Task to by a vastly simpler construct to use that hit's 95% of the use cases for RxJS observables. The task modifiers (flow control modifiers in Vuency?) combined with generator fn syntax hits a very nice (if somewhat pragmatic) sweet spot between declarative and imperative that has cleaned up so much of my apps' code that used to suffer from Rx-style "purity".
@machty Managing the hookup between Observables and Observers does require tactical thought but it is a flexible not just a one size fits all solution. For a good example of automatically managing subscriptions look no further than the vue-rx plugin. Like xstream it also makes Observables hot by default, so that does not need to be the stumbling block: https://github.com/vuejs/vue-rx
I'm quite familiar with CycleJS and the RxJS world and do not recall seeing typeahead search code that uses the do operator the way you point out. It is the designed purpose of the do operator to handle side-effects: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35#do
My major RxJS requirements are outside the Task box, I need the full power of being able to create Observables and hook them up to Observers in cold or hot scenarios. RxJS is a polyglot paradigm that is deeply embraced on all platforms. It has many imitators but certainly can not be substituted.
To each their own.
The robustness of RxJS can be overwhelming, which can result in extra layers of middleware. Such middleware can also keep devs from owning up to and learning the real deal, for only short term gain. My own concern is that I like both Vue and RxJS the way they are and anything extra should offer me something I don't already have.
I'm going to keep following the RxJS highway 💯
It sounds like you've been in the Rx camp so long that you're numb to the boilerplate and lack of conventions.
I agree with @MVSICA-FICTA in that we should try to build upon whatever exists, especially if existing solutions solve the same problem just as well. Nonetheless, you could say that Ember, React, Vue etc. solve the same problem, but in their own unique way that different developers resonate with. So I think it's incongruous to completely discriminate against the possibility of other options if you use Vuejs (and not older, more tested frameworks).
In regards to tasks, the reason I resonate with them (compared to my first encounter with observables) is that as @machty mentioned, they offer a nice balance between the declarative and imperative approach. This allows Vuency to offer a lightweight and flexible solution that is easier for developers to embrace and understand. So even if Tasks
and Observables
overlap, it's a question of the extent that they do take the same approach and the extra benefits that they offer. I disagree that it's about "learning the real deal", I think the simpler you can make the solution, the better.
But the reason I started this issue was not to compare which solution to concurrency is better or worse (though I am interested in learning the differences) but to figure out how they could perhaps be incorporated to give developers more power. So I'd love to make this a productive and fruitful discussion.
I'm going to be playing around with Observables and vue-rx later this week so that I can have a more informed opinion.
@machty Is there a reason you didn't incorporate observables into ember-concurrency? Do you think it makes any sense to use them with tasks?
@MVSICA-FICTA If you think of examples of how they can work together, please let me know. Meanwhile, I'm going to be putting more examples up so that users can get a better idea of the power and usage of tasks.
@alidcastano That is the real objective, thanks for pulling it back on track. I also like the concept of Task units and I'm sure Observables can find their way into them like they do everywhere else.
Over the same next days you mentioned I'll be digging through a lot of RxJS and engineering how that all fits into the Vue VM. Working off my own design and thinking of Tasks now as well, cheers!
@MVSICA-FICTA I spent the day learning about Observables, here are my thoughts:
There is obviously overlap between Tasks and Observables, as they’re both primitives targeted at solving common problems with async I/O operations.
But at the core, their respective solutions approach the problem from different angles.
In ReactiveRx’s solution with Observables, the primitive is more like an upgraded array, intended to pull a stream of data from events
In Vuency’s (and E-C’s) solution with Tasks, the primitive is more like an upgraded function, intended to handle a stream of events with data.
That’s the subtle difference between the two: Observables
manipulate data pulled from events; Tasks
handle events fired with data.
The observer pattern is great for handling streams of data and business logic since it allows us to manipulate and compose it in a functional and declarative manner.
The task pattern is great for handling concurrent events and UI interactions since it allows us to control the flow of repeat requests while giving access to the operation's state, all with minimal boilerplate.
Let me now example my initial problem with observables.
Let's start with a simple example:Â
Rx.Observable.fromEvent($input, ‘keyup’)
.pluck(‘target’, ‘value’)
.filter(text => text.length > 2 );
As of now, this is similar to doing functional programming with native array methods, with the added benefit that observers can handle streams of data received from events.
What gets complicated about this, is that the same observer chain can be used, not only on data stream itself, but also on event where the data come from.
In our previous example, if we also wanted to manage how repeat calls to the same event are handled, the code would look like this:
Rx.Observable.fromEvent($input, ‘keyup’)
.debounce(500)
.pluck(‘target’, ‘value’)
.filter(text => text.length > 2 );
Not being accustomed to the observable pattern, your left wondering: how can array-like operations and function-like operations be used on the same source? This is possible because, one, the observer
(event) is attached to the observable
(data) upon subscription
so you're actually operating on both sources, and, two, because the order that you chain the operations matters (e.g. you can’t filter the data and then try to debounce the event). However, having everything tied together with similar names makes it difficult to wrap your head around how everything works together.
If the handling of the data were separated from the handling of the event, the entire operation would be easier to understand.
This is where Tasks can come in:
import { t, obs } from 'vuency'
t(function * (args) {
return yield obs(args) /* observable - data operations */
.pluck(‘target’, ‘value’)
.filter(text => text.length > 2)
}).onEvent('keyup').flow('restart') /* observer - event operations */
}
All data that the operation is called with could be converted into an observer, so that inside the event function, it could be composed in a functional and declarative manner.
There would be two main benefits of this:
Thus, Tasks have the potential of making observers easier to understand and use.
The only potential difficulty I see with using rxjs
implementation of the observer pattern is the degree that they have to overlap with tasks. If it's too much, it might make more sense for tasks to implement their own observable pattern so that they could remain lightweight and flexible.
But it seems like I can make it so that the observer only has the methods that I import, and thus I can compose it how I like. For example:
import {Observable} from "rxjs/Observable";
import "rxjs/add/operator/map";
Observable
.map(x => x + "!!!")
If that's the case, and we can use Observables without the Scheduling options (Vuency's scheduler handles flow control while updating the tasks state), then it would definitely make sense to build upon this existing solution.
This week, I'm going to work on extracting the parts of Vuency that are just JS so that it can be used as general javascript library for underlying tasks primitives. While I'm at it, I'll also be experimenting with ways that observables can be incorporated into tasks. So far I plan on doing it based on the pattern described here, but I'm open to discussion.
"That’s the subtle difference between the two: Observables manipulate data pulled from events; Tasks handle events fired with data." Then Observable also push events to Observers.
IMO what the real issue is has to do with ES6 Generators versus RxJS Observables, they are different paradigms. I remember getting a headache about this difference and then RxJS exploded on the scene. Here are just a few quick links about this difference:
http://sitr.us/2014/08/02/javascript-generators-and-functional-reactive-programming.html http://stackoverflow.com/questions/41349033/how-does-observables-rx-js-compare-to-es2015-generators
An of course eggheads pay for the gem of knowledge take: https://egghead.io/lessons/rxjs-observables-push-compared-to-generator-functions-pull
Here, I'm plotting along and getting happier about how Vue, Vue-Rx and RxJS are coming together, without ES6 Generators involved. I essentially work with the Vue-Rx v-stream directive, creating intent Subjects and putting them in the domStream array, which is an aspect of Vue-Rx.
From there I map the intent Subjects to general action/task wrapper functions, with a high-level universal function for each major group of tasks. So for example, all calibration (like sliders without the slider part) actions use the same wrapper function, which has a given intent Subject mapped to it (a given intent Subject can be targeted from multiple divs). There are then more of these wrappers for other major action/task sets.
Then there are simple methods that implement the Observer contract (next, error, complete). The Observers are separate from the Observable wrapper functions, because Observers each do their own thing with the state produced by a given wrapper. So there can also be multiple Observers for a given wrapper.
What I've been wrestling with is if I would need dynamic (JIT-wiring) subscribing of the Observers to the Observable wrappers. I'm learning more and just maybe the automatic subscribe (pre-wiring to make everything hot) might work as is.
The thing that also helps out here is the RxJS let operator and it's new enhancements coming soon, see https://github.com/ReactiveX/rxjs/pull/2529
I strive to keep business logic out of components so logic is largely extracted out to service modules. So only the essential infrastructure intent Subjects, wrapped Observables and simple Observer shells fulfilling the contract remain in the Vue VM. Service modules are then injected and mapped into the infrastructure functions. Super modular, easy to reason about and testable. I'm still very much developing this reactivity graph that is hosted by components.
So my take away from the past days is realizing that RxJS offers two architectural levels. The macro level of Observables and Observers and the flexible way of connecting them. Then the micro level of plugging service functions into the infrastructure functions via the let operator and it's soon to be released enhancements.
Yea I guess it would be more accurate to say that Observables pull data from events that they then push to observers so that they can process it?
And are you saying that Observers can/should be used with tasks? Or that your needs seem to be fulfilled with vue-rx?
If the former, can you provide code samples to conceptualize the usage? (The terminology is difficult to tie together, which IMO is one of the problems.)
For definitions I just go with the ones found here: http://reactivex.io/rxjs/manual/overview.html
If you were to use Observer functions for tasks then that just splits things into two sides, the Observable (producer) and the Observer (consumer). In RxJS these are distinct but operators like the creation operator can obscure this by just attaching the next callback of the three Observer contract functions directly to itself.
I like to use these two sides as separate things/functions, then connect them like nodes in a graph with subscriptions. I'm not saying they can/should be used with tasks, just that this is how it works in RxJS and it affords a lot of architectural possibilities.
To me the Observable is a processing engine that uses the Operators to process whatever events are coming through, this works like a pipeline. This engine is not turned on until some Observer subscribes to it. If it is a Subject then multiple Observers can receive the same processed stream.
Vue-Rx does only a few things and then I'm building out the full deck of tool related Observables and Observers from there. The basic v-stream to Subject to Observer is essential but for my needs there is a bigger chain reaction scenario I'm in the process of building.
So yes, Vue-Rx is a starting point but my real needs are only going to be satisfied by my own effort, there is simply no major architecture for logic processing to be found elsewhere. Have to leverage RxJS and Vue and go from there, directed forward by my app requirements.
Terminology is indeed thick and this is why I'm also still near the tail end of doing a comprehensive study of RxJS. Then to top it off working in Vue there is also the builtin reactivity system. I call these the major (RxJS) and minor (Vue) reactivity systems. Then of course there are Generators and CSP and what not.
This being the case I really have a lot of work ahead, carefully study and application of RxJS to my custom reactive graph. Looking at a few more weeks before I come out with a verified and completed solution. I'm proving out RxJS by exercising it's API against a set of killer app features, should be fun :)
Time flies and I've taken a detour to look at a few architecture patterns that are emerging in the Observable frontier. For my project this exercise has confirmed and clarified a few points yet I'm still on the same trajectory of building my reactive graph as I already was.
Maybe some of the emerging architectures would be useful to you, if not just for an idea of how the RxJS community is beginning to build a more task like flow around Observables? Here are some of the more exceptional ones:
http://codewinds.com/blog/2016-08-16-business-logic-redux.html https://redux-observable.js.org http://www.christianalfoni.com/articles/2017_04_16_The-second-case-for-function-tree
All of these projects are working at solving a similar set of issues. One of the things I find most remarkable is how Redux style action creators are now being reworked to become a form of control flow in Observable functions. In other words, they are being pulled out of the classic usage in Redux and instead being used to orchestrate process flow through diverse tasks.
Its like the humble and awkward action creator/message is in the process of being transformed into a new type of if/switch statement, weaved right into the producers themselves. Every architecture listed above seemingly has their own take on this, myself included.
Perhaps they can be used as additional task modifiers or injected into the
tasks
property function to be used the task's internal operation.