Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/tracker/package.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package.describe({
summary: "Dependency tracker to allow reactive callbacks",
version: "1.2.1"
version: "1.3.4"
});

Package.onUse(function (api) {
Expand Down
16 changes: 15 additions & 1 deletion packages/tracker/tracker.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export namespace Tracker {
* True during the initial run of the computation at the time `Tracker.autorun` is called, and false on subsequent reruns and at other times.
*/
firstRun: boolean;
/**
* Forces autorun blocks to be executed in synchronous-looking order by storing the value autorun promise thus making it awaitable.
*/
firstRunPromise: Promise<unknown>
/**
* Invalidates this computation so that it will be rerun.
*/
Expand Down Expand Up @@ -48,7 +52,7 @@ export namespace Tracker {
* The current computation, or `null` if there isn't one. The current computation is the `Tracker.Computation` object created by the innermost active call to
* `Tracker.autorun`, and it's the computation that gains dependencies when reactive data sources are accessed.
*/
var currentComputation: Computation;
var currentComputation: Computation | null;

var Dependency: DependencyStatic;
/**
Expand Down Expand Up @@ -109,6 +113,16 @@ export namespace Tracker {
}
): Computation;

/**
* @summary Helper function to make the tracker work with promises.
* @param computation Computation that tracked
* @param func a function that needs to be called and be reactive. This may be async or not - but this function is typically called after an await
*/
function withComputation<T>(
computation: Computation | null,
func: () => T
): T;

/**
* Process all reactive updates immediately and ensure that all invalidated computations are rerun.
*/
Expand Down
72 changes: 55 additions & 17 deletions packages/tracker/tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ Tracker.active = false;
*/
Tracker.currentComputation = null;

function setCurrentComputation(c) {
Tracker.currentComputation = c;
Tracker.active = !! c;
}

function _debugFunc() {
// We want this code to work without Meteor, and also without
// "console" (which is technically non-standard and may be missing
Expand Down Expand Up @@ -201,6 +196,16 @@ Tracker.Computation = class Computation {
this._onError = onError;
this._recomputing = false;

/**
* @summary Forces autorun blocks to be executed in synchronous-looking order by storing the value autorun promise thus making it awaitable.
* @locus Client
* @memberOf Tracker.Computation
* @instance
* @name firstRunPromise
* @returns {Promise<unknown>}
*/
this.firstRunPromise = undefined;

var errored = true;
try {
this._compute();
Expand All @@ -212,6 +217,22 @@ Tracker.Computation = class Computation {
}
}


/**
* Resolves the firstRunPromise with the result of the autorun function.
* @param {*} onResolved
* @param {*} onRejected
* @returns{Promise<unknown}
*/
then(onResolved, onRejected) {
return this.firstRunPromise.then(onResolved, onRejected);
};


catch(onRejected) {
return this.firstRunPromise.catch(onRejected)
};

// http://docs.meteor.com/#computation_oninvalidate

/**
Expand Down Expand Up @@ -300,14 +321,21 @@ Tracker.Computation = class Computation {
_compute() {
this.invalidated = false;

var previous = Tracker.currentComputation;
setCurrentComputation(this);
var previousInCompute = inCompute;
inCompute = true;

try {
withNoYieldsAllowed(this._func)(this);
// In case of async functions, the result of this function will contain the promise of the autorun function
// & make autoruns await-able.
const firstRunPromise = Tracker.withComputation(this, () => {
return withNoYieldsAllowed(this._func)(this);
});
// We'll store the firstRunPromise on the computation so it can be awaited by the callers, but only
// during the first run. We don't want things to get mixed up.
if (this.firstRun) {
this.firstRunPromise = Promise.resolve(firstRunPromise);
}
} finally {
setCurrentComputation(previous);
inCompute = previousInCompute;
}
}
Expand Down Expand Up @@ -566,15 +594,12 @@ Tracker._runFlush = function (options) {
* thrown. Defaults to the error being logged to the console.
* @returns {Tracker.Computation}
*/
Tracker.autorun = function (f, options) {
Tracker.autorun = function (f, options = {}) {
if (typeof f !== 'function')
throw new Error('Tracker.autorun requires a function argument');

options = options || {};

constructingComputation = true;
var c = new Tracker.Computation(
f, Tracker.currentComputation, options.onError);
var c = new Tracker.Computation(f, Tracker.currentComputation, options.onError);

if (Tracker.active)
Tracker.onInvalidate(function () {
Expand All @@ -597,12 +622,25 @@ Tracker.autorun = function (f, options) {
* @param {Function} func A function to call immediately.
*/
Tracker.nonreactive = function (f) {
var previous = Tracker.currentComputation;
setCurrentComputation(null);
return Tracker.withComputation(null, f);
};

/**
* @summary Helper function to make the tracker work with promises.
* @param computation Computation that tracked
* @param func a function that needs to be called and be reactive. This may be async or not - but this function is typically called after an await
*/
Tracker.withComputation = function (computation, f) {
var previousComputation = Tracker.currentComputation;

Tracker.currentComputation = computation;
Tracker.active = !!computation;

try {
return f();
} finally {
setCurrentComputation(previous);
Tracker.currentComputation = previousComputation;
Tracker.active = !!previousComputation;
}
};

Expand Down