Skip to content
Permalink
Browse files

Move guards from auto binding to event dispatch

This wraps a proper guard around event dispatching so that errors doesn't
interupt other event callbacks.

Instead we rethrow the first error after all callbacks have been invoked.

For DEV mode we use native event dispatching if available. This has the
benefit that caught exceptions show up in the dev tools even without caught
exceptions being turned on. Yet, all callbacks are guaranteed to fire.
  • Loading branch information...
sebmarkbage committed Jun 20, 2015
1 parent 1b67acc commit 16cc45156f65ff7fdda57383759121f59f585e41
@@ -13,7 +13,6 @@

var ReactComponent = require('ReactComponent');
var ReactElement = require('ReactElement');
var ReactErrorUtils = require('ReactErrorUtils');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
@@ -705,10 +704,7 @@ function bindAutoBindMethods(component) {
var method = component.__reactAutoBindMap[autoBindKey];
component[autoBindKey] = bindAutoBindMethod(
component,
ReactErrorUtils.guard(
method,
component.constructor.displayName + '.' + autoBindKey
)
method
);
}
}
@@ -161,6 +161,34 @@ describe('ReactBrowserEventEmitter', function() {
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});

it('should continue bubbling if an error is thrown', function() {
ReactBrowserEventEmitter.putListener(
getID(CHILD),
ON_CLICK_KEY,
recordID.bind(null, getID(CHILD))
);
ReactBrowserEventEmitter.putListener(
getID(PARENT),
ON_CLICK_KEY,
function() {
recordID(getID(PARENT));
throw new Error('Handler interrupted');
}
);
ReactBrowserEventEmitter.putListener(
getID(GRANDPARENT),
ON_CLICK_KEY,
recordID.bind(null, getID(GRANDPARENT))
);
expect(function() {
ReactTestUtils.Simulate.click(CHILD);
}).toThrow();
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(getID(CHILD));
expect(idCallOrder[1]).toBe(getID(PARENT));
expect(idCallOrder[2]).toBe(getID(GRANDPARENT));
});

it('should set currentTarget', function() {
ReactBrowserEventEmitter.putListener(
getID(CHILD),
@@ -13,6 +13,7 @@

var EventPluginRegistry = require('EventPluginRegistry');
var EventPluginUtils = require('EventPluginUtils');
var ReactErrorUtils = require('ReactErrorUtils');

var accumulateInto = require('accumulateInto');
var forEachAccumulated = require('forEachAccumulated');
@@ -282,6 +283,8 @@ var EventPluginHub = {
'processEventQueue(): Additional events were enqueued while processing ' +
'an event queue. Support for this has not yet been implemented.'
);
// This would be a good time to rethrow if any of the event handlers threw.
ReactErrorUtils.rethrowCaughtError();
},

/**
@@ -12,6 +12,7 @@
'use strict';

var EventConstants = require('EventConstants');
var ReactErrorUtils = require('ReactErrorUtils');

var invariant = require('invariant');
var warning = require('warning');
@@ -108,7 +109,9 @@ function forEachEventDispatch(event, cb) {
*/
function executeDispatch(event, listener, domID) {
event.currentTarget = injection.Mount.getNode(domID);
var returnValue = listener(event, domID);
var type = event.type || 'unknown-event';
var returnValue =
ReactErrorUtils.invokeGuardedCallback(type, listener, event, domID);
event.currentTarget = null;
return returnValue;
}

This file was deleted.

@@ -12,19 +12,57 @@

'use strict';

var caughtError = null;

var ReactErrorUtils = {
/**
* Creates a guarded version of a function. This is supposed to make debugging
* of event handlers easier. To aid debugging with the browser's debugger,
* this currently simply returns the original function.
* Call a function while guarding against errors that happens within it.
*
* @param {function} func Function to be executed
* @param {string} name The name of the guard
* @return {function}
* @param name (?String) name of the guard to use for logging or debugging
* @param func (Function) function to invoke
* @param a (*) a First argument
* @param b (*) b Second argument
*/
invokeGuardedCallback: function(name, func, a, b) {
try {
return func(a, b);
} catch(x) {
if (caughtError === null) {
caughtError = x;
}
return undefined;
}
},

/**
* During execution of guarded functions we will capture the first error which
* we will rethrow to be handled by the top level error handler.
*/
guard: function(func, name) {
return func;
rethrowCaughtError: function() {
if (caughtError) {
var error = caughtError;
caughtError = null;
throw error;
}
},
};

if (__DEV__) {
/**
* To help development we can get better devtools integration by simulating a
* real browser event.
*/
if (typeof window !== 'undefined' &&
typeof window.dispatchEvent === 'function' &&
typeof Event === 'function') {
var fakeNode = document.createElement('react');
ReactErrorUtils.invokeGuardedCallback = function(name, func, a, b) {
var boundFunc = func.bind(null, a, b);
fakeNode.addEventListener(name, boundFunc, false);
fakeNode.dispatchEvent(new Event(name));
fakeNode.removeEventListener(name, boundFunc, false);
};
}
}

module.exports = ReactErrorUtils;

0 comments on commit 16cc451

Please sign in to comment.
You can’t perform that action at this time.