How does Meteor's reactivity work behind the scenes? - meteor

I have read the docs and looked at the source behind reactivity, but I don't understand it.
Can someone explain how this works behind the scenes, as it looks like magic to me :).

So it's actually rather straight forward, at a basic level there are 2 types of functions involved:
Functions that create a reactive context (reactive function)
Functions that invalidate a reactive context (invalidating function)
Functions that can do both. (I lied there are 3)
When you call a reactive function it creates a context that meteor stores globally and to which the reactive function subscribes an invalidation callback. The function that you pass to a reactive function, or any functions that run from within it, can be an invalidating function and can grab the current context and store it locally. These functions can then at any time, like on a db update or simply a timer call, invalidate that context. The original reactive function would then receive that event and re-evaluate itself.
Here's a step by step using meteor functions (note that Tracker.autorun used to be called Deps.autorun):
Tracker.autorun(function(){
alert("Hello " + Session.get("name"));
});
Session.set("name", "Greg");
autorun takes a function as its parameter
before autorun runs this function, it creates a context
autorun attaches a callback to the context's invalidation event
This callback will re-run the function passed to autorun
The function is then run in the context for the first time.
Meteor stores this context globally as the currently active context
Inside the function is another function: Session.get()
Session.get() is both a reactive function and an invalidating function
Session.get sets up it's own context and associates it internally with the key "name"
Session.get retrieves the current context (autorun's context) globally from meteor
The invalidation callback that Session.get registers to it's own context, will simply invalidate it's enclosing context (in this case, autorun's context)
So now we have 2 contexts, autorun's and session.get's
when these functions return, meteor cleans up the active context global variable
Session.set is another function capable of invalidating a context.
in this case we're invalidating all contexts created by Session associated with the key "name"
All of those contexts, when invalidated, run their invalidation callbacks.
Those callbacks just invalidate their enclosing contexts (That's the design of Session.get and not what a invalidation callback must do)
Those enclosing contexts now run their invalidation callbacks.
In the autorun case, that callback runs the function we originally passed to autorun and then sets up the context again.
The whole implementation is actually rather straight forward as well, you can see it here:
https://github.com/meteor/meteor/blob/master/packages/tracker/tracker.js
And a good example of how it works can be found here:
https://github.com/meteor/meteor/blob/master/packages/reactive-dict/reactive-dict.js
Reactive programming is not actually meteor or JS specific
you can read about it here: http://en.wikipedia.org/wiki/Reactive_programming

Related

Meteor reactive-var package is missing the equals() method, is this a bug?

I'm learning about reactive programming in Meteor:
https://stephenwalther.com/archive/2014/12/05/dont-do-react-understanding-meteor-reactive-programming
I believe that the idea behind Session.equals(key, value) is to remember an association between the reactive variable and the desired value so that updates only propagate to the surrounding code if the equality changes. That way if we have hundreds of views that depend on the variable, only the old and new views get their update code triggered when the value changes.
Note that this would not be the case if we called Session.get(key) === value because every view's code would be called when the variable changes. This is discussed further under the Session.get versus Session.equals() section of the article.
But I found an inconsistency under the Using Reactive Variables section where it says:
Notice that a reactive variable, unlike the Session object, does not have an equals() method. Yes, that is a shame.
So reactive-var is missing equals() but reactive-dict has ReactiveDict.equals().
I can't really see a conceptual reason to exclude ReactiveVar.equals(). Maybe they had no context for storing the association, or maybe there is some scoping or other issue with Javascript that prevents this that I don't fully understand.
So my question is: is this a bug?
Should I just always use reactive-dict? In which case I would change everything from:
let myReactiveVar = new ReactiveVar();
...
if(myReactiveVar.get() === 'myValue')
To the more verbose (but performant):
let myReactiveDict = new ReactiveDict();
...
if(myReactiveDict.equals('myReactiveVar', 'myValue'))
Which would match the functionality provided by Session.equals().
Another option would be to extend the ReactiveVar prototype with my own equals() method or inherit it in a child class and provide a MyReactiveVar.equals() method. Kudos if someone can provide examples to do either of these workarounds that we could submit as a pull request to the Meteor maintainers.
Update: I forgot to mention that ReactiveVar does take an equalsFunc optional parameter in its constructor. It might be possible to hack that as a reactive code block to partially implement equals() functionality without extending the class. Also, here is a related issue on GitHub.
Update: to save time, here is the relevant source code for ReactiveVar and ReactiveDict.equals(). I believe that the value parameter gets converted to serializedValue and is then added as a dependency in ReactiveDict, but I still don't see why it wouldn't be possible to do something similar for ReactiveVar.
The reason there's no equals method for ReactiveVar is because set only invalidates the computations is the new value differs from the current value.
Sets the current value of the ReactiveVar, invalidating the Computations that called get if newValue is different from the old value.
const example = new ReactiveVar(0);
Tracker.autorun(() => {
console.log(example.get());
});
example.set(1); // logs 1
example.set(0); // logs 0
example.set(0); // doesn't log
This is similar behaviour to ReactiveDict's equals method.
Note that set on ReactiveDict does not behave this way. Calling set broadcasts that the value has changed. If you want to prevent the computation from invalidating, that is when you would use equals.
Set a value for a key in the ReactiveDict. Notify any listeners that the value has changed (eg: redraw templates, and rerun any Tracker.autorun computations, that called ReactiveDict.get on this key.)

What data types can be passed to redux-thunk actions?

Problem overview
I am using redux-form, and it wraps my compoent, and provides as a property change() which is a bound function for changing the form. I wanted to invoke this in an actionCreator.
Issue
I thought I could just pass it to my actionCreator thunk, but it seems that the value is stripped on dispatch of the action.
Details
So what I tried instead of sending the bound action change I tried instead to send a pointer to one of my other actionCreators called act.setFMEPATH, but that is also not working.
In the first picture below I show the state of the variables in before the dispatach to the redux-action occurs. As you can see from the watch, act.setFMEPath clearly is defined.
In the second picture you see the state of the action when it is entered. defaultPath is define (it is scoped to a closure on the stack), but the storeAct.
In my code, I am hard coding the STORE.dispatch(Act.setFMEPath(filePath[0]) since the paramater storeAct is not visible, But what I want it STORE.dispatch(storeAct(filePath[0])
Should I be binding the context of the first parameter to the dispatch() and turning storeAct() into a bound function (but note it is an arrow function, so that is not going to work).
State before call:
State entry of pickFile action creator
The code which invokes pickFolder (i.e. Act.pickFolder(Act.setSourcePath, defaultPath)) has nothing to do with redux-thunk at all. It will be evaluated before redux-thunk will come into play, because of the way redux-thunk works.
redux-thunk checks if dispatched value was a function and if it was, redux-thunk invokes it with dispatch, getState and extraArgument params. Please check its source code, it's really tiny and simple.
Basically it means exactly what Radio said: if it's undefined in pickFolder that's because it was undefined in the caller. Also I don't see any usages of destination in your pickFolder, so maybe check for the typos.
It seems that redux is not happy with not plan javascript object. As shown above, when I tried to dispatch an actionCreator which had a bound function call as a parameter, it was missing when the actionCreator was created.
I also tried to create a reducer that placed a function handle (bound function) into the store, and it was not stored, and the prior value was removed (a placeholder).
I am puzzled by what #Radio- is saying, since what I am seeing is that redux does not like things that are not plain javascript objects (either as parameters to the dispatch call, or as values from the reducer to be placed in the store).

When to use the promise returned by ractive.set?

The ractive.set method returns a promise. When performing a simple set operation (single value or map) and then immediately referencing the new value via ractive.get, is it recommended to use the promise? Or is that completely unnecessary?
I've been avoiding the promise and found that I don't need it, but maybe I've just been lucky so far. Here's an example of what I mean:
ractive.set("foo", "bar");
console.log(ractive.get("foo")); // always outputs the correct value "bar"
I'm worried that the set operation is asynchronous and this will become evident on slower machines or if I start using the more advanced features of Ractive.
According to the Ractive docs:
[ractive.set] Returns a Promise that will be called after the set
operation and any transitions are complete.
Based on that, I wonder if the promise is really meant for post-transition work.
Based on that, I wonder if the promise is really meant for
post-transition work.
Exactly. The value update (and the resulting DOM changes per the template) happen synchronously, the promise is meant for asynchronous response to end of transitions.
This is also why the set operation also has a hash map option for the input parameters so multiple sets will be batched in one go:
ractive.set({
foo: 'foo',
bar: 'bar'
}).then( () => {
// this happens asynchronously ***after*** code execution has
// continued below on next event cycle or after transitions complete
});
// data and DOM have been updated as the code continues synchronously here:
console.log( ractive.get() );

how does Tracker.autorun pick out its computation?

Looking at Tracker.autorun, this mostly works magically...but I'd like to know how it decides which variables are going to form the dependency for the computation. It picks out only "reactive" vars, for example the following:
window.bar = 1
Tracker.autorun (c) =>
bar = window.bar
foo = Session.get('foo')
console.log('autorun', foo, bar)
If I change the value of Session.set('foo') this will cause the computation to run again.
whereas just changing window.bar doesn't cause a rerun. If I use a subscribe result (not a collection) this also works, so that I guess is reactive too.
Are there any guides to understanding this behavior a bit better?
EDIT:
thanks for the comments below that clarify the computation is able to be inferred because accessors are used for reactive vars, so meteor can track deps.
However I need a bit more clarity to understand when a var is flagged. for instance in this example below, the subscribe call is outside the autorun, but it puts the results into an array. So that means that Tracker is not just tracking calls to (reactive var) accessor methods, but also any variables that are referenced within a block - even if the calls to setup those methods are outside the autorun() block.
subList = [
Meteor.subscribe("Players"),
Meteor.subscribe("Stuff" )
]
Tracker.autorun (c) =>
subReady = _.filter subList, (item) ->
return item.ready()
allDone = (subList.length == subReady.length)
# this code will rerun when the subs ready() are true
maybe i should add this as a new question... it's related to this question .
I'm not an expert and haven't read much about it, but I can try to explain it briefly.
All reactive variables have a thing called a dependency. For example, when one creates a new ReactiveVar, an new dependency is created. See here.
To retrieve the value from a reactive variable, one must call a function. In that "getter", the dependency is instructed to remember that it has a dependency. For example, see here for ReactiveVar.get.
To change the value for a reactive variable, one must call a function. In that "setter", the dependency is notified that something has changed, and that signals that all functions depending on the dependency must rerun. For example, see here for ReactiveVar.set.
Not complicated, right? Well, that was just the easy part, all that remains now is building the infrastructure that makes it work :) That's harder and more complicated to explain.
Reactive variables aren't reactive by themselves; they must be evaluated in a reactive environment in order to be reactive. A reactive environment is created by calling Tracker.autorun. See here.
When you call Tracker.autorun, the function you passed to it will be executed in a new reactive environment, and all dependencies the reactive variables notifies of with the depend method will be tracked by the environment. When you call aDependency.depend, this function will be executed, and it kind of adds the dependency to the environments list over dependencies it depends on.
When a reactive variable changes its value, this function will be executed. It tells the environment that one of the reactive variables it depends on has changed, and invalidates all the dependencies in the environment. After this has happened, the entire function you passed to Tracker.autorun will be re-run, and the new dependencies will be tracked.
Do you get the big picture? It's implementation is a bit more complicated than I've explained, but I think that's kind of how it works.
Notice that whenever you access a reactive variable, it's through a function call, like Session.get(...), or collection.find(...).fetch(), or Meteor.status(). A function like Session.get does not only get the value of a Session variable, but also registers a dependency on the current computation (the current computation is dynamically scoped, so Session.get knows that it was called from an autorun).
Here's how you can implement your own reactive variable using Tracker.Dependency:
dependency = new Tracker.Dependency()
currentValue = null
#setCurrentValue = (newValue) ->
if newValue isnt currentValue
# rerun computations which depend on this Dependency
dependency.changed()
currentValue = newValue
#getCurrentValue = ->
# register this dependency on the current computation (if there is one)
dependency.depend()
return currentValue
And here's how you could use it:
setCurrentValue("hello")
Tracker.autorun ->
console.log(getCurrentValue())
# => "hello" printed
setCurrentValue("goodbye") # => "goodbye" printed
For more information, you could look at this guide: https://meteor.hackpad.com/DRAFT-Understanding-Deps-aAXG6T9lkf6 (note that Tracker was originally called Deps in older versions of Meteor)

Meteor wrapAsync syntax

How do I use the Meteor wrapAsync?
Below is what I am trying to do
if (tempTreatment.groupId === undefined) {
// create new group
Meteor.wrapAsync(Meteor.call('createTreatmentGroup', salon, tempTreatment.groupName, tempTreatment.groupName));
// get group id
var getGroup = Meteor.wrapAsync(Meteor.call('getTreatmentGroup', salon, tempTreatment.groupName));
console.log(getGroup);
tempTreatment.groupId = getGroup._id;
}
I want to run these two Meteor.callfunctions synchronosly but I get undefined on console.log(getGroup); which shuold just return an object.
Meteor.wrapAsync is a server-side API designed to wrap Node.js asynchronous functions requiring a callback as last argument, to make them appear synchronous through the use of Futures, a Fibers sub-library. (more on this here : https://www.discovermeteor.com/blog/wrapping-npm-packages/)
It is not intended to be used client-side to turn asynchronous Meteor.call into a synchronous call because on the browser, Remote Method Invokation calls are ALWAYS asynchronous.
Long story short, you simply cannot achieve what you're trying to do, you have to use callbacks and nest your second method call inside the success callback of your first method call.

Resources