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

Tweaks to error reporting in Tracker. #3822

Closed
wants to merge 5 commits into from
Closed
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
5 changes: 5 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
* Yield to the event loop during the flush cycle, unless we're executing a
synchronous `Tracker.flush()`. #3901

* Fix error reporting not being source-mapped properly. #3655

* Introduce a new option for `Tracker.autorun` - `onError`. This callback can be
used to handle errors caught in the reactive computations. #3822


### `meteor` command-line tool

Expand Down
48 changes: 43 additions & 5 deletions docs/client/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,17 @@ DocsData = {
"longname": "Accounts.setPassword",
"memberof": "Accounts",
"name": "setPassword",
"options": [],
"options": [
{
"description": "<p>Logout all current connections with this userId (default: true)</p>",
"name": "logout",
"type": {
"names": [
"Object"
]
}
}
],
"params": [
{
"description": "<p>The id of the user to update.</p>",
Expand All @@ -513,6 +523,15 @@ DocsData = {
"String"
]
}
},
{
"name": "options",
"optional": true,
"type": {
"names": [
"Object"
]
}
}
],
"scope": "static",
Expand Down Expand Up @@ -5392,7 +5411,7 @@ DocsData = {
"options": [],
"params": [],
"scope": "static",
"summary": "- Inside an `onCreated`, `onRendered`, or `onDestroyed` callback, returns\nthe data context of the template.\n- Inside a helper, returns the data context of the DOM node where the helper\nwas used.\n- Inside an event handler, returns the data context of the element that fired\nthe event.\n\nEstablishes a reactive dependency on the result."
"summary": "- Inside an `onCreated`, `onRendered`, or `onDestroyed` callback, returns\nthe data context of the template.\n- Inside an event handler, returns the data context of the template on which\nthis event handler was defined.\n- Inside a helper, returns the data context of the DOM node where the helper\nwas used.\n\nEstablishes a reactive dependency on the result."
},
"Template.dynamic": {
"istemplate": "true",
Expand Down Expand Up @@ -5730,16 +5749,35 @@ DocsData = {
"longname": "Tracker.autorun",
"memberof": "Tracker",
"name": "autorun",
"options": [],
"options": [
{
"description": "<p>Optional. The function to run when an error\nhappens in the Computation. The only argument it recieves is the Error\nthrown. Defaults to the error being logged to the console.</p>",
"name": "onError",
"type": {
"names": [
"function"
]
}
}
],
"params": [
{
"description": "<p>The function to run. It receives one argument: the Computation object that will be returned.</p>",
"description": "<p>The function to run. It receives\none argument: the Computation object that will be returned.</p>",
"name": "runFunc",
"type": {
"names": [
"Tracker.ComputationFunction"
]
}
},
{
"name": "options",
"optional": true,
"type": {
"names": [
"Object"
]
}
}
],
"returns": [
Expand All @@ -5752,7 +5790,7 @@ DocsData = {
}
],
"scope": "static",
"summary": "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."
"summary": "Run a function now and rerun it later whenever its dependencies\nchange. Returns a Computation object that can be used to stop or observe the\nrerunning."
},
"Tracker.currentComputation": {
"kind": "member",
Expand Down
5 changes: 5 additions & 0 deletions packages/meteor/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ Meteor._debug = function (/* arguments */) {
Meteor._suppress_log = function (count) {
suppress += count;
};

Meteor._supressed_log_expected = function () {
return suppress !== 0;
};

1 change: 1 addition & 0 deletions packages/tracker/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Package.onUse(function (api) {

Package.onTest(function (api) {
api.use('tinytest');
api.use('test-helpers');
api.use('tracker');
api.addFiles('tracker_tests.js', 'client');
});
67 changes: 48 additions & 19 deletions packages/tracker/tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,42 @@ var _debugFunc = function () {
//
// Lazy evaluation because `Meteor` does not exist right away.(??)
return (typeof Meteor !== "undefined" ? Meteor._debug :
((typeof console !== "undefined") && console.log ?
function () { console.log.apply(console, arguments); } :
((typeof console !== "undefined") && console.error ?
function () { console.error.apply(console, arguments); } :
function () {}));
};

var _maybeSupressMoreLogs = function (messagesLength) {
// Sometimes when running tests, we intentionally supress logs on expected
// printed errors. Since the current implementation of _throwOrLog can log
// multiple separate log messages, supress all of them if at least one supress
// is expected as we still want them to count as one.
if (typeof Meteor !== "undefined") {
if (Meteor._supressed_log_expected()) {
Meteor._suppress_log(messagesLength - 1);
}
}
};

var _throwOrLog = function (from, e) {
if (throwFirstError) {
throw e;
} else {
var messageAndStack;
if (e.stack && e.message) {
var printArgs = ["Exception from Tracker " + from + " function:"];
if (e.stack && e.message && e.name) {
var idx = e.stack.indexOf(e.message);
if (idx >= 0 && idx <= 10) // allow for "Error: " (at least 7)
messageAndStack = e.stack; // message is part of e.stack, as in Chrome
else
messageAndStack = e.message +
(e.stack.charAt(0) === '\n' ? '' : '\n') + e.stack; // e.g. Safari
} else {
messageAndStack = e.stack || e.message;
if (idx < 0 || idx > e.name.length + 2) { // check for "Error: "
// message is not part of the stack
var message = e.name + ": " + e.message;
printArgs.push(message);
}
}
printArgs.push(e.stack);
_maybeSupressMoreLogs(printArgs.length);

for (var i = 0; i < printArgs.length; i++) {
_debugFunc()(printArgs[i]);
}
_debugFunc()("Exception from Tracker " + from + " function:",
messageAndStack);
}
};

Expand Down Expand Up @@ -136,7 +150,7 @@ var constructingComputation = false;
* computation.
* @instancename computation
*/
Tracker.Computation = function (f, parent) {
Tracker.Computation = function (f, parent, onError) {
if (! constructingComputation)
throw new Error(
"Tracker.Computation constructor is private; use Tracker.autorun");
Expand Down Expand Up @@ -185,6 +199,7 @@ Tracker.Computation = function (f, parent) {
// to constrain the order that computations are processed
self._parent = parent;
self._func = f;
self._onError = onError;
self._recomputing = false;

// Register the computation within the global Tracker.
Expand Down Expand Up @@ -297,7 +312,11 @@ Tracker.Computation.prototype._recompute = function () {
try {
self._compute();
} catch (e) {
_throwOrLog("recompute", e);
if (self._onError) {
self._onError(e);
} else {
_throwOrLog("recompute", e);
}
}
}
} finally {
Expand Down Expand Up @@ -492,17 +511,27 @@ Tracker._runFlush = function (options) {
* @param {Tracker.Computation}
*/
/**
* @summary 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.
* @summary 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.
* @locus Client
* @param {Tracker.ComputationFunction} runFunc The function to run. It receives one argument: the Computation object that will be returned.
* @param {Tracker.ComputationFunction} runFunc The function to run. It receives
* one argument: the Computation object that will be returned.
* @param {Object} [options]
* @param {Function} options.onError Optional. The function to run when an error
* happens in the Computation. The only argument it recieves is the Error
* thrown. Defaults to the error being logged to the console.
* @returns {Tracker.Computation}
*/
Tracker.autorun = function (f) {
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);
var c = new Tracker.Computation(
f, Tracker.currentComputation, options.onError);

if (Tracker.active)
Tracker.onInvalidate(function () {
Expand Down
18 changes: 18 additions & 0 deletions packages/tracker/tracker_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,21 @@ Tinytest.add('tracker - Tracker.flush finishes', function (test) {
Tracker.flush();
test.equal(n, 2000);
});

testAsyncMulti('tracker - Tracker.autorun, onError option', [function (test, expect) {
var d = new Tracker.Dependency;
var c = Tracker.autorun(function (c) {
d.depend();

if (! c.firstRun)
throw new Error("foo");
}, {
onError: expect(function (err) {
test.equal(err.message, "foo");
})
});

d.changed();
Tracker.flush();
}]);