Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add firstRunPromise #12824

Merged
merged 21 commits into from Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4654adb
Allow for await-able autoruns by adding firstRunPromise to computatio…
DanielDornhardt Oct 4, 2023
1a7ad0d
Merge branch 'devel' into tracker-computation-firstRunPromise
StorytellerCZ Oct 11, 2023
3a8e09a
Apply @radekmie suggestions
harryadel Oct 12, 2023
262a6dc
Merge branch 'devel' of github.com:harryadel/meteor into HEAD
harryadel Oct 12, 2023
6106a2c
Merge branch 'devel' into tracker-firstRunPromise
Grubba27 Nov 6, 2023
ff615a5
[tracker] add then and catch
harryadel Nov 19, 2023
2eb32ba
Merge branch 'tracker-firstRunPromise' of github.com:harryadel/meteor…
harryadel Nov 19, 2023
4fc44cb
Merge branch 'devel' into tracker-firstRunPromise
StorytellerCZ Nov 21, 2023
f2c98ae
[tracker] await then & catch
harryadel Nov 22, 2023
8223aef
Merge branch 'tracker-firstRunPromise' of github.com:harryadel/meteor…
harryadel Nov 22, 2023
e41c8ec
Update packages/tracker/tracker.js
harryadel Nov 28, 2023
2e32f1c
[tracker] Add a test without firstRunPromise
harryadel Nov 29, 2023
a30c131
[tracker] attach methods without the use of 'this'
harryadel Nov 29, 2023
e91c5c2
Merge branch 'devel' into tracker-firstRunPromise
harryadel Nov 29, 2023
8888925
Fixed missing firstRunPromise.
radekmie Nov 29, 2023
cc07fe5
[tracker] Remove joke because Gabriel is no fun :sob:
harryadel Dec 4, 2023
8e90991
[tracker] Use of firstRunPromise is not necessary paragraph
harryadel Dec 4, 2023
01acda0
Merge branch 'release-2.14' into tracker-firstRunPromise
StorytellerCZ Dec 4, 2023
654f366
Merge branch 'release-2.14' into tracker-firstRunPromise
StorytellerCZ Dec 4, 2023
0afa7df
Solve error in jsdoc generation
Grubba27 Dec 4, 2023
c47c3a1
Adjusting typo
Grubba27 Dec 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/source/api/tracker.md
Expand Up @@ -302,6 +302,26 @@ recomputed at flush time.
This property is a convenience to support the common pattern where a
computation has logic specific to the first run.

{% apibox "Tracker.Computation#firstRunPromise" %}

`Computation.firstRunPromise` will be set to the result of the call of the autorun function after the initial computation has been completed. If the autorun function is an async function, it'll then contain its promise, thus making the completion of the execution await-able. That allows us to manually synchronize autoruns like this:

```js

await Tracker.autorun(async () => {
await Meteor.userAsync();
(...more async code...)
}).firstRunPromise;

await Tracker.autorun(async () => {
await asyncSomeOrOther();
(...more async code...)
}).firstRunPromise;

// ta-daa! We'll get here only after both the autorun functions will have returned & executed in their entirety.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe remove this comment? we are already explaining this in the paragraph above.
Add examples that are not using .firstRunPromise as well.


```
<h2 id="tracker_dependency"><span>Tracker.Dependency</span></h2>

A Dependency represents an atomic unit of reactive data that a
Expand Down
4 changes: 4 additions & 0 deletions packages/tracker/tracker.d.ts
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
46 changes: 38 additions & 8 deletions packages/tracker/tracker.js
Expand Up @@ -196,6 +196,31 @@ 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
harryadel marked this conversation as resolved.
Show resolved Hide resolved
* @memberOf Tracker.Computation
* @instance
* @name firstRunPromise
* @returns {Promise<unknown>}
*/
this.firstRunPromise = undefined;
radekmie marked this conversation as resolved.
Show resolved Hide resolved

/**
*
* @param {*} onResolved
* @param {*} onRejected
* @returns
*/
this.then = async function (onResolved, onRejected) {
await this.firstRunPromise.then(onResolved, onRejected);
radekmie marked this conversation as resolved.
Show resolved Hide resolved
};


this.catch = async function (onRejected) {
await this.firstRunPromise.catch(onRejected)
};
radekmie marked this conversation as resolved.
Show resolved Hide resolved

var errored = true;
try {
this._compute();
Expand Down Expand Up @@ -297,10 +322,18 @@ Tracker.Computation = class Computation {

var previousInCompute = inCompute;
inCompute = true;

try {
Tracker.withComputation(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, () => {
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 = firstRunPromise;
}
} finally {
inCompute = previousInCompute;
}
Expand Down Expand Up @@ -560,21 +593,18 @@ 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 () {
c.stop();
});

return c;
};

Expand Down
50 changes: 49 additions & 1 deletion packages/tracker/tracker_tests.js
Expand Up @@ -297,7 +297,7 @@ Tinytest.add("tracker - lifecycle", function (test) {
test.equal(c, Tracker.currentComputation);
test.equal(c.stopped, false);
test.equal(c.invalidated, false);
test.equal(c.firstRun, firstRun);
test.equal(c.firstRun, firstRun);

Tracker.onInvalidate(makeCb()); // 1, 6, ...
Tracker.afterFlush(makeCb()); // 2, 7, ...
Expand Down Expand Up @@ -630,6 +630,54 @@ Tinytest.addAsync('tracker - async function - stepped', async function (test) {
test.equal(count, limit, 'after resolve');
});


Tinytest.addAsync('tracker - async function - synchronize', async test => {
radekmie marked this conversation as resolved.
Show resolved Hide resolved
let counter = 0;

await Tracker.autorun(async () => {
test.equal(counter, 0);
counter += 1;
test.equal(counter, 1);
await Promise.resolve();
test.equal(counter, 1);
counter *= 2;
test.equal(counter, 2);
});

await Tracker.autorun(async () => {
test.equal(counter, 2);
counter += 1;
test.equal(counter, 3);
await Promise.resolve();
test.equal(counter, 3);
counter *= 2;
test.equal(counter, 6);
});
})

Tinytest.addAsync('tracker - async function - synchronize - firstRunPromise', async test => {
let counter = 0
await Tracker.autorun(async () => {
test.equal(counter, 0);
counter += 1;
test.equal(counter, 1);
await Promise.resolve();
test.equal(counter, 1);
counter *= 2;
test.equal(counter, 2);
}).firstRunPromise;

await Tracker.autorun(async () => {
test.equal(counter, 2);
counter += 1;
test.equal(counter, 3);
await Promise.resolve();
test.equal(counter, 3);
counter *= 2;
test.equal(counter, 6);
}).firstRunPromise;
})

Tinytest.add('computation - #flush', function (test) {
var i = 0, j = 0, d = new Tracker.Dependency;
var c1 = Tracker.autorun(function () {
Expand Down