This commit is contained in:
David Greenspan
2013-03-04 18:09:13 -08:00
parent 4f3645ee37
commit 7e90bc9268
2 changed files with 74 additions and 106 deletions

View File

@@ -2189,79 +2189,84 @@ Returns a handle that can be used by `Meteor.clearInterval`.
{{> api_box clearTimeout}}
{{> api_box clearInterval}}
<h2 id="meteor_deps"><span>Meteor.deps</span></h2>
<h2 id="deps"><span>Deps</span></h2>
Meteor has a simple dependency tracking system, so that it it can
automatically rerender templates and such when [`Session`](#session)
variables are modified, or database queries change.
Meteor has a simple dependency tracking system which allows it to
automatically rerun templates and other data consumers whenever
[`Session`](#session) variables, database queries, and other data
sources change.
Unlike most other systems, you don't have to manually declare these
dependencies &mdash; it "just works". The mechanism is simple and
efficient. When you call a function that supports reactive updates
(say, a database query), it automatically saves the current
"invalidation context" object if any (say, the current template being
rendered). Later, when the data changes, it can "invalidate" this
context (tell the template to rerender itself). The whole
implementation is about 50 lines of code.
(such as a database query), it automatically saves the current
Computation object, if any (representing, for example, the current
template being rendered). Later, when the data changes, the function
can "invalidate" the Computation, causing it to rerun (rerendering the
template).
Developers, particularly package authors, can use *invalidation
contexts* to implement additional reactive data sources or to write
functions that automatically register dependencies on reactive data
sources.
Applications are expected to use [`Deps.run`](#deps_run) and
[`Deps.flush`](#dep_flush) directly, while more advanced facilities
are useful for package authors implementing new reactive data sources.
{{> api_box Context }}
{{> api_box deps_run }}
Create an invalidation context by calling this constructor, then run
some code inside the context with [`run`](#run). Finally, register a
callback with [`onInvalidate`](#oninvalidate) that will get called
when the code you run wants to signal that it should be rerun.
`Deps.run` allows you to run a function that depends on reactive data sources, in such a way that if there are changes to the data later, the function will be rerun.
Code can see if it's running inside an invalidation context by reading
the [`Meteor.deps.Context.current`](#current) global variable, which
will be the currently active context, or `null` if it's not being run
from inside a context. If it wants to participate in the reactivity
system, it should save this context away, and later call the
[`invalidate`](#invalidate) method on the context when it wants to
signal that something has changed. If it does this, it should also use
[`onInvalidate`](#oninvalidate) to set up a cleanup function so that
it can know when to stop listening for changes.
For example, you can monitor a cursor (which is a reactive data source) and aggregate it into a session variable:
Invalidation contexts have an `id` attribute, which is a unique positive
integer, and a boolean attribute `invalidated`. `invalidated` starts out false,
and is set to true when `invalidate` is called, before calling any of the
invalidation callbacks. You're free to add any other attributes you like to the
invalidation context for your own convenience, as long as they don't start with
an underscore.
Deps.run(function () {
var oldest = _.max(Monkeys.find().fetch(), function (monkey) {
return monkey.age;
});
if (oldest)
Session.set("oldest", oldest.name);
});
{{> api_box run }}
Or you can wait for a session variable to have a certain value, and do something the first time it does, calling `stop` on the computation to prevent further rerunning:
This function simply sets [`Meteor.deps.Context.current`](#current) to
this invalidation context, runs `func`, and then restores it to its
previous value. It returns the result of calling `func`.
Deps.run(function (c) {
if (! Session.equals("shouldAlert", true))
return;
It's fine for `run` to be called recursively. `current` will return the
innermost context.
c.stop();
alert("Oh no!");
});
{{> api_box onInvalidate }}
The function is invoked immediately, at which point it may alert and stop right away if `shouldAlert` is already true. If not, the function is run again when `shouldAlert` becomes true.
If this context hasn't been invalidated yet, adds `callback` to the list
of callbacks that will be called when [`invalidate`](#invalidate) is
called. If the context has already been invalidated, call `callback`
immediately.
Reruns do not occur immediately when the data dependency changes, but are deferred until "flush time", which is scheduled for after whatever code made the change has finished running. You can use [`Deps.flush`](#deps_flush) to force reactive updates to happen sooner.
Typically this function will have two kinds of callers:
If you nest calls to `Deps.run`, then when the outer call stops or reruns, the inner call will stop automatically. Subscriptions and observers are also automatically stopped when used as part of a computation that is rerun.
* The function that creates the invalidation context will use the
`onInvalidate` callback as a signal to rerun the code in the context,
to see what new value it returns. In order to rerun the code, it'll
create a fresh invalidation context and reregister its `onInvalidate`
callback on that new context. When that context is invalidated the
cycle will repeat.
<h2 id="deps_computation"><span>Deps.Computation</span></h2>
* Functions that are sources of reactive data will save
[`Meteor.deps.Context.current`](#current) into some kind of list of
listeners. They'll use the `onInvalidate` callback to remove the
context from their listener list.
A Computation object represents code that is repeatedly rerun in response to reactive data changes. Computations don't have return values, they just perform actions, such as rerendering a template on the screen. They are created using [`Deps.run`](#deps_run). Use [`stop`](#computation_stop) to prevent further rerunning of a computation.
Each time the computation runs, it may access various reactive data sources that serve as inputs to the computation, which are called its dependencies. At some future time, one of these dependencies may trigger the computation to be rerun by invalidating it. When this happens, the computation will be rerun at next flush time, at which point the old dependencies are cleared.
The *current computation* ([`Deps.currentComputation`](#deps_current_computation)) is the computation that is currently being run or rerun, and the one that gains a dependency when a reactive data source is accessed. Data sources are responsible for tracking these dependencies using [`Deps.Variable`](#deps_variable) objects.
Stopping or invalidating a computation will cause it to be processed at next flush time, or later in the current flush if one is in progress. Processing a stopped or invalidated computation involves these steps:
* Call (and clear) all `onInvalidate` callbacks
* If the computation is not stopped, rerun it and mark it valid again
* Call (and clear) all `afterInvalidate` callbacks
Callbacks can only be added to a computation while the computation is active, meaning its function is being run or rerun (it may be the current computation or one that encloses it). In particular, you can't register new callbacks from within a callback.
Example:
// if we're in a computation, then perform some clean-up
// when this computation is invalidated (rerun or stopped)
if (Deps.active) {
Deps.onInvalidate(function () {
x.destroy();
y.finalize();
});
}
XXXXXXXX
Example:
@@ -2362,35 +2367,6 @@ might think of it as a dynamically scoped ("special") variable. (That
just means that [`run`](#run) sets it, runs some user-supplied code, and
then restores its previous value.)
{{> api_box autorun }}
`Meteor.autorun` allows you to set up your own reactive context, where you can
perform arbitrary actions when dependencies change. For example, you can monitor
a cursor (which is a reactive data source) and aggregate it into a session
variable:
Meteor.autorun(function() {
var oldest = _.max(Monkeys.find().fetch(), function (monkey) {
return monkey.age;
});
if (oldest)
Session.set("oldest", oldest.name);
});
Or you can wait for a session variable to get a certain value, and do something
the first time it does so, using the `stop` handle to prevent further runs:
Meteor.autorun(function(handle) {
if (!Session.equals("shouldAlert", true)) return;
handle.stop();
alert("Oh no!");
});
The function is invoked immediately and &mdash; like all reactive
sources &mdash; the rerun occurs at the time of the next
[`Meteor.flush`](#meteor_flush).
{{> api_box flush }}
Normally, when you make changes (like writing to the database),

View File

@@ -724,24 +724,16 @@ Template.api.fieldspecifiers = {
name: "Field Specifiers"
};
Template.api.Context = {
id: "context",
name: "new Meteor.deps.Context",
locus: "Client",
descr: ["Create an invalidation context. Invalidation contexts are used to run a piece of code, and record its dependencies so it can be rerun later if one of its inputs changes.", "An invalidation context is basically just a list of callbacks for an event that can fire only once. The [`onInvalidate`](#oninvalidate) method adds a callback to the list, and the [`invalidate`](#invalidate) method fires the event."]
};
// Template.api.Computation = {
// id: "computation",
// name: "new Deps.Computation(runFunc)",
// locus: "Client",
// descr: ["Create and run a new computation. Use [`Deps.run`](#deps_run) in preference to this constructor."
// + ""
// + "Computations represent code that must be rerun when data changes that it depends on. When a computation runs, any reactive data sources that it accesses are considered dependencies, which may later invalidate the computation, causing it to be rerun.
Template.api.run = {
id: "run",
name: "<em>context</em>.run(func)",
locus: "Client",
descr: ["Run some code inside an evaluation context."],
args: [
{name: "func",
type: "Function",
descr: "The code to run"}
]
};
// "Create an invalidation context. Invalidation contexts are used to run a piece of code, and record its dependencies so it can be rerun later if one of its inputs changes.", "An invalidation context is basically just a list of callbacks for an event that can fire only once. The [`onInvalidate`](#oninvalidate) method adds a callback to the list, and the [`invalidate`](#invalidate) method fires the event."]
// };
Template.api.onInvalidate = {
id: "oninvalidate",
@@ -769,15 +761,15 @@ Template.api.current = {
descr: ["The current [`invalidation context`](#context), or `null` if not being called from inside [`run`](#run)."]
};
Template.api.autorun = {
id: "meteor_autorun",
name: "Meteor.autorun(func)",
Template.api.deps_run = {
id: "deps_run",
name: "Deps.run(func)",
locus: "Client",
descr: ["Run a function and rerun it whenever its dependencies change. Returns a handle that provides a `stop` method, which will prevent further reruns."],
descr: ["Run a function now and rerun it later whenever its dependencies change. Returns a Computation object that can be used to stop or observe the rerunning."],
args: [
{name: "func",
type: "Function",
descr: "The function to run. It receives one argument: the same handle that `Meteor.autorun` returns."}
descr: "The function to run. It receives one argument: the Computation object that will be returned."}
]
};