From 34d30f52ce137ca018fe02ca3cac2d6d0a0f064e Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 2 Jul 2014 20:44:14 -0700 Subject: [PATCH] Catch exceptions thrown from template helpers --- packages/{ui => blaze}/exceptions.js | 24 ++++++++++++++++-------- packages/blaze/lookup.js | 10 +++++++--- packages/blaze/package.js | 1 + 3 files changed, 24 insertions(+), 11 deletions(-) rename packages/{ui => blaze}/exceptions.js (64%) diff --git a/packages/ui/exceptions.js b/packages/blaze/exceptions.js similarity index 64% rename from packages/ui/exceptions.js rename to packages/blaze/exceptions.js index 0da25c2e4a..2488f7bee2 100644 --- a/packages/ui/exceptions.js +++ b/packages/blaze/exceptions.js @@ -1,11 +1,6 @@ -// XXX This is dead code but we should probably still do something like this. -// Change "Meteor UI" to "Blaze". Actually, "Exception in Blaze" is cryptic -// and misleading; better to make it clear the fault is in user code, as in -// "Exception in 'created' callback" etc. - var debugFunc; -// Meteor UI calls into user code in many places, and it's nice to catch exceptions +// We call into user code in many places, and it's nice to catch exceptions // propagated from user code immediately so that the whole system doesn't just // break. Catching exceptions is easy; reporting them is hard. This helper // reports exceptions. @@ -22,7 +17,7 @@ var debugFunc; // // An optional second argument overrides the default message. -reportUIException = function (e, msg) { +Blaze.reportException = function (e, msg) { if (! debugFunc) // adapted from Deps debugFunc = function () { @@ -34,5 +29,18 @@ reportUIException = function (e, msg) { // In Chrome, `e.stack` is a multiline string that starts with the message // and contains a stack trace. Furthermore, `console.log` makes it clickable. // `console.log` supplies the space between the two arguments. - debugFunc()(msg || 'Exception in Meteor UI:', e.stack || e.message); + debugFunc()(msg || 'Exception caught in template:', e.stack || e.message); +}; + +Blaze.wrapCatchingExceptions = function (f, where) { + if (typeof f !== 'function') + return f; + + return function () { + try { + return f.apply(this, arguments); + } catch (e) { + Blaze.reportException(e, 'Exception in ' + where + ':'); + } + }; }; diff --git a/packages/blaze/lookup.js b/packages/blaze/lookup.js index 7c861480c7..86a1e4fed4 100644 --- a/packages/blaze/lookup.js +++ b/packages/blaze/lookup.js @@ -18,6 +18,10 @@ var bindToCurrentDataIfIsFunction = function (x) { return x; }; +var wrapHelper = function (f) { + return Blaze.wrapCatchingExceptions(f, 'template helper'); +}; + // Implements {{foo}} where `name` is "foo" // and `component` is the component the tag is found in // (the lexical "self," on which to look for methods). @@ -37,15 +41,15 @@ Blaze.View.prototype.lookup = function (name, _options) { return Blaze._parentData(name.length - 1); } else if (template && (name in template)) { - return bindToCurrentDataIfIsFunction(template[name]); + return wrapHelper(bindToCurrentDataIfIsFunction(template[name])); } else if (lookupTemplate && Template.__lookup__(name)) { return Template.__lookup__(name); } else if (UI._globalHelpers[name]) { - return bindToCurrentDataIfIsFunction(UI._globalHelpers[name]); + return wrapHelper(bindToCurrentDataIfIsFunction(UI._globalHelpers[name])); } else { var data = Blaze.getCurrentData(); if (data) - return bindIfIsFunction(data[name], data); + return wrapHelper(bindIfIsFunction(data[name], data)); } return null; }; diff --git a/packages/blaze/package.js b/packages/blaze/package.js index 1e2eef6a72..d9d0d48cb6 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -27,6 +27,7 @@ Package.on_use(function (api) { // client and server api.add_files([ + 'exceptions.js', 'reactivevar.js', 'view.js', 'builtins.js',