UNPKG

10.6 kBJavaScriptView Raw
1//////////////////////////////////////////////////
2// Package docs at http://docs.meteor.com/#deps //
3//////////////////////////////////////////////////
4
5var Deps =
6module.exports = {};
7
8// http://docs.meteor.com/#deps_active
9Deps.active = false;
10
11// http://docs.meteor.com/#deps_currentcomputation
12Deps.currentComputation = null;
13
14var setCurrentComputation = function (c) {
15 Deps.currentComputation = c;
16 Deps.active = !! c;
17};
18
19// _assign is like _.extend or the upcoming Object.assign.
20// Copy src's own, enumerable properties onto tgt and return
21// tgt.
22var _hasOwnProperty = Object.prototype.hasOwnProperty;
23var _assign = function (tgt, src) {
24 for (var k in src) {
25 if (_hasOwnProperty.call(src, k))
26 tgt[k] = src[k];
27 }
28 return tgt;
29};
30
31var _debugFunc = function () {
32 // lazy evaluation because `Meteor` does not exist right away
33 return (typeof Meteor !== "undefined" ? Meteor._debug :
34 ((typeof console !== "undefined") && console.log ?
35 function () { console.log.apply(console, arguments); } :
36 function () {}));
37};
38
39var _throwOrLog = function (from, e) {
40 if (throwFirstError) {
41 throw e;
42 } else {
43 _debugFunc()("Exception from Deps " + from + " function:",
44 e.stack || e.message);
45 }
46};
47
48// Like `Meteor._noYieldsAllowed(function () { f(comp); })` but shorter,
49// and doesn't clutter the stack with an extra frame on the client,
50// where `_noYieldsAllowed` is a no-op. `f` may be a computation
51// function or an onInvalidate callback.
52var callWithNoYieldsAllowed = function (f, comp) {
53 if ((typeof Meteor === 'undefined') || Meteor.isClient) {
54 f(comp);
55 } else {
56 Meteor._noYieldsAllowed(function () {
57 f(comp);
58 });
59 }
60};
61
62var nextId = 1;
63// computations whose callbacks we should call at flush time
64var pendingComputations = [];
65// `true` if a Deps.flush is scheduled, or if we are in Deps.flush now
66var willFlush = false;
67// `true` if we are in Deps.flush now
68var inFlush = false;
69// `true` if we are computing a computation now, either first time
70// or recompute. This matches Deps.active unless we are inside
71// Deps.nonreactive, which nullfies currentComputation even though
72// an enclosing computation may still be running.
73var inCompute = false;
74// `true` if the `_throwFirstError` option was passed in to the call
75// to Deps.flush that we are in. When set, throw rather than log the
76// first error encountered while flushing. Before throwing the error,
77// finish flushing (from a finally block), logging any subsequent
78// errors.
79var throwFirstError = false;
80
81var afterFlushCallbacks = [];
82
83var requireFlush = function () {
84 if (! willFlush) {
85 setTimeout(Deps.flush, 0);
86 willFlush = true;
87 }
88};
89
90// Deps.Computation constructor is visible but private
91// (throws an error if you try to call it)
92var constructingComputation = false;
93
94//
95// http://docs.meteor.com/#deps_computation
96//
97Deps.Computation = function (f, parent) {
98 if (! constructingComputation)
99 throw new Error(
100 "Deps.Computation constructor is private; use Deps.autorun");
101 constructingComputation = false;
102
103 var self = this;
104
105 // http://docs.meteor.com/#computation_stopped
106 self.stopped = false;
107
108 // http://docs.meteor.com/#computation_invalidated
109 self.invalidated = false;
110
111 // http://docs.meteor.com/#computation_firstrun
112 self.firstRun = true;
113
114 self._id = nextId++;
115 self._onInvalidateCallbacks = [];
116 // the plan is at some point to use the parent relation
117 // to constrain the order that computations are processed
118 self._parent = parent;
119 self._func = f;
120 self._recomputing = false;
121
122 var errored = true;
123 try {
124 self._compute();
125 errored = false;
126 } finally {
127 self.firstRun = false;
128 if (errored)
129 self.stop();
130 }
131};
132
133_assign(Deps.Computation.prototype, {
134
135 // http://docs.meteor.com/#computation_oninvalidate
136 onInvalidate: function (f) {
137 var self = this;
138
139 if (typeof f !== 'function')
140 throw new Error("onInvalidate requires a function");
141
142 if (self.invalidated) {
143 Deps.nonreactive(function () {
144 callWithNoYieldsAllowed(f, self);
145 });
146 } else {
147 self._onInvalidateCallbacks.push(f);
148 }
149 },
150
151 // http://docs.meteor.com/#computation_invalidate
152 invalidate: function () {
153 var self = this;
154 if (! self.invalidated) {
155 // if we're currently in _recompute(), don't enqueue
156 // ourselves, since we'll rerun immediately anyway.
157 if (! self._recomputing && ! self.stopped) {
158 requireFlush();
159 pendingComputations.push(this);
160 }
161
162 self.invalidated = true;
163
164 // callbacks can't add callbacks, because
165 // self.invalidated === true.
166 for(var i = 0, f; f = self._onInvalidateCallbacks[i]; i++) {
167 Deps.nonreactive(function () {
168 callWithNoYieldsAllowed(f, self);
169 });
170 }
171 self._onInvalidateCallbacks = [];
172 }
173 },
174
175 // http://docs.meteor.com/#computation_stop
176 stop: function () {
177 if (! this.stopped) {
178 this.stopped = true;
179 this.invalidate();
180 }
181 },
182
183 _compute: function () {
184 var self = this;
185 self.invalidated = false;
186
187 var previous = Deps.currentComputation;
188 setCurrentComputation(self);
189 var previousInCompute = inCompute;
190 inCompute = true;
191 try {
192 callWithNoYieldsAllowed(self._func, self);
193 } finally {
194 setCurrentComputation(previous);
195 inCompute = false;
196 }
197 },
198
199 _recompute: function () {
200 var self = this;
201
202 self._recomputing = true;
203 try {
204 while (self.invalidated && ! self.stopped) {
205 try {
206 self._compute();
207 } catch (e) {
208 _throwOrLog("recompute", e);
209 }
210 // If _compute() invalidated us, we run again immediately.
211 // A computation that invalidates itself indefinitely is an
212 // infinite loop, of course.
213 //
214 // We could put an iteration counter here and catch run-away
215 // loops.
216 }
217 } finally {
218 self._recomputing = false;
219 }
220 }
221});
222
223//
224// http://docs.meteor.com/#deps_dependency
225//
226Deps.Dependency = function () {
227 this._dependentsById = {};
228};
229
230_assign(Deps.Dependency.prototype, {
231 // http://docs.meteor.com/#dependency_depend
232 //
233 // Adds `computation` to this set if it is not already
234 // present. Returns true if `computation` is a new member of the set.
235 // If no argument, defaults to currentComputation, or does nothing
236 // if there is no currentComputation.
237 depend: function (computation) {
238 if (! computation) {
239 if (! Deps.active)
240 return false;
241
242 computation = Deps.currentComputation;
243 }
244 var self = this;
245 var id = computation._id;
246 if (! (id in self._dependentsById)) {
247 self._dependentsById[id] = computation;
248 computation.onInvalidate(function () {
249 delete self._dependentsById[id];
250 });
251 return true;
252 }
253 return false;
254 },
255
256 // http://docs.meteor.com/#dependency_changed
257 changed: function () {
258 var self = this;
259 for (var id in self._dependentsById)
260 self._dependentsById[id].invalidate();
261 },
262
263 // http://docs.meteor.com/#dependency_hasdependents
264 hasDependents: function () {
265 var self = this;
266 for(var id in self._dependentsById)
267 return true;
268 return false;
269 }
270});
271
272_assign(Deps, {
273 // http://docs.meteor.com/#deps_flush
274 flush: function (_opts) {
275 // XXX What part of the comment below is still true? (We no longer
276 // have Spark)
277 //
278 // Nested flush could plausibly happen if, say, a flush causes
279 // DOM mutation, which causes a "blur" event, which runs an
280 // app event handler that calls Deps.flush. At the moment
281 // Spark blocks event handlers during DOM mutation anyway,
282 // because the LiveRange tree isn't valid. And we don't have
283 // any useful notion of a nested flush.
284 //
285 // https://app.asana.com/0/159908330244/385138233856
286 if (inFlush)
287 throw new Error("Can't call Deps.flush while flushing");
288
289 if (inCompute)
290 throw new Error("Can't flush inside Deps.autorun");
291
292 inFlush = true;
293 willFlush = true;
294 throwFirstError = !! (_opts && _opts._throwFirstError);
295
296 var finishedTry = false;
297 try {
298 while (pendingComputations.length ||
299 afterFlushCallbacks.length) {
300
301 // recompute all pending computations
302 while (pendingComputations.length) {
303 var comp = pendingComputations.shift();
304 comp._recompute();
305 }
306
307 if (afterFlushCallbacks.length) {
308 // call one afterFlush callback, which may
309 // invalidate more computations
310 var func = afterFlushCallbacks.shift();
311 try {
312 func();
313 } catch (e) {
314 _throwOrLog("afterFlush function", e);
315 }
316 }
317 }
318 finishedTry = true;
319 } finally {
320 if (! finishedTry) {
321 // we're erroring
322 inFlush = false; // needed before calling `Deps.flush()` again
323 Deps.flush({_throwFirstError: false}); // finish flushing
324 }
325 willFlush = false;
326 inFlush = false;
327 }
328 },
329
330 // http://docs.meteor.com/#deps_autorun
331 //
332 // Run f(). Record its dependencies. Rerun it whenever the
333 // dependencies change.
334 //
335 // Returns a new Computation, which is also passed to f.
336 //
337 // Links the computation to the current computation
338 // so that it is stopped if the current computation is invalidated.
339 autorun: function (f) {
340 if (typeof f !== 'function')
341 throw new Error('Deps.autorun requires a function argument');
342
343 constructingComputation = true;
344 var c = new Deps.Computation(f, Deps.currentComputation);
345
346 if (Deps.active)
347 Deps.onInvalidate(function () {
348 c.stop();
349 });
350
351 return c;
352 },
353
354 // http://docs.meteor.com/#deps_nonreactive
355 //
356 // Run `f` with no current computation, returning the return value
357 // of `f`. Used to turn off reactivity for the duration of `f`,
358 // so that reactive data sources accessed by `f` will not result in any
359 // computations being invalidated.
360 nonreactive: function (f) {
361 var previous = Deps.currentComputation;
362 setCurrentComputation(null);
363 try {
364 return f();
365 } finally {
366 setCurrentComputation(previous);
367 }
368 },
369
370 // http://docs.meteor.com/#deps_oninvalidate
371 onInvalidate: function (f) {
372 if (! Deps.active)
373 throw new Error("Deps.onInvalidate requires a currentComputation");
374
375 Deps.currentComputation.onInvalidate(f);
376 },
377
378 // http://docs.meteor.com/#deps_afterflush
379 afterFlush: function (f) {
380 afterFlushCallbacks.push(f);
381 requireFlush();
382 }
383});
\No newline at end of file