Repository: sveltejs/svelte (JavaScript) — Bug Fix

Issue:
Event handler removed after callback fails
Hello,

I found what I think is a bug.

I listen to windows keyup events in a child component, so I can add and remove this component in a #if block (because my component is kind of a lightbox slideshow so most of the time it is not visible, so I want the event listeners to be removed).

In the parent component, I listen to the events fired by the child and handle them with a method.

What I found is that if the method throws an exception, the event handler of the parent will not be called anymore, but the child one still will be.

[Here is an REPL demo](https://svelte.technology/repl?version=2.9.0&gist=059975893b9b70aaee0c25c186b50bfc).

Steps to reproduce : 
1. Go to the REPL and open your console (I tried chrome and Firefox, last stable ones)
2. When loaded, click on the rendered area so the editor will not swallow keyboard events
3. Push keys on your keyboard, but NOT the ArrowLeft key. You will see that the rendered area show what key you pushed. Look at the console, you will see logs from both components.
4. Push the ArrowLeft key, you will see an exception in the console.
5. Push keys on your keyboard, the View is not refreshed. Look at the console, you will see that the child component is handling window events, but not the parent component.

I have a [repository](https://github.com/niahoo/sveltejs-bug) too.

thank you



Discussion:
What's happening here is the `__calling` flag on the handler is getting set to true, and then never set to false again because of the exception, and so the `this.fire(...)` doesn't actually call any more handlers for this event.

In other areas, Svelte has taken the position that not having unhandled exceptions is the developer's responsibility. There's also the question of what to do with other handlers that would have been called after the one that threw the exception. We don't want to just silently swallow the exception.

I guess my proposal of how to deal with this situation is; If a handler throws an exception, make sure we set `__calling` back to false, but then re-throw the exception from within `fire` and don't continue with the rest of the handlers.
Agreed, this seems the way native events work too.

Resolving patch:
diff --git a/src/shared/index.js b/src/shared/index.js
--- a/src/shared/index.js
+++ b/src/shared/index.js
@@ -47,9 +47,12 @@ export function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }


Test patch:
diff --git a/test/cli/samples/basic/expected/Main.js b/test/cli/samples/basic/expected/Main.js
--- a/test/cli/samples/basic/expected/Main.js
+++ b/test/cli/samples/basic/expected/Main.js
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/custom-element/expected/Main.js b/test/cli/samples/custom-element/expected/Main.js
--- a/test/cli/samples/custom-element/expected/Main.js
+++ b/test/cli/samples/custom-element/expected/Main.js
@@ -121,9 +121,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dev/expected/Main.js b/test/cli/samples/dev/expected/Main.js
--- a/test/cli/samples/dev/expected/Main.js
+++ b/test/cli/samples/dev/expected/Main.js
@@ -121,9 +121,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir-sourcemap/expected/Main.js b/test/cli/samples/dir-sourcemap/expected/Main.js
--- a/test/cli/samples/dir-sourcemap/expected/Main.js
+++ b/test/cli/samples/dir-sourcemap/expected/Main.js
@@ -1,4 +1,4 @@
-/* src/Main.html generated by Svelte v2.7.2 */
+/* src/Main.html generated by Svelte vx.y.z */
 import Widget from './Widget.html';
 
 
@@ -108,9 +108,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir-sourcemap/expected/Main.js.map b/test/cli/samples/dir-sourcemap/expected/Main.js.map
--- a/test/cli/samples/dir-sourcemap/expected/Main.js.map
+++ b/test/cli/samples/dir-sourcemap/expected/Main.js.map
@@ -1 +1 @@
-{"version":3,"file":"Main.js","sources":["../src/Main.html"],"sourcesContent":["<Widget/>\n\n<script>\n\timport Widget from './Widget.html';\n\n\texport default {\n\t\tcomponents: {\n\t\t\tWidget\n\t\t}\n\t};\n</script>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"Main.js","sources":["../src/Main.html"],"sourcesContent":["<Widget/>\n\n<script>\n\timport Widget from './Widget.html';\n\n\texport default {\n\t\tcomponents: {\n\t\t\tWidget\n\t\t}\n\t};\n</script>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/test/cli/samples/dir-sourcemap/expected/Widget.js b/test/cli/samples/dir-sourcemap/expected/Widget.js
--- a/test/cli/samples/dir-sourcemap/expected/Widget.js
+++ b/test/cli/samples/dir-sourcemap/expected/Widget.js
@@ -1,4 +1,4 @@
-/* src/Widget.html generated by Svelte v2.7.2 */
+/* src/Widget.html generated by Svelte vx.y.z */
 
 function create_main_fragment(component, ctx) {
 	var p;
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir-sourcemap/expected/Widget.js.map b/test/cli/samples/dir-sourcemap/expected/Widget.js.map
--- a/test/cli/samples/dir-sourcemap/expected/Widget.js.map
+++ b/test/cli/samples/dir-sourcemap/expected/Widget.js.map
@@ -1 +1 @@
-{"version":3,"file":"Widget.js","sources":["../src/Widget.html"],"sourcesContent":["<p>widget</p>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"Widget.js","sources":["../src/Widget.html"],"sourcesContent":["<p>widget</p>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/test/cli/samples/dir-subdir/expected/Main.js b/test/cli/samples/dir-subdir/expected/Main.js
--- a/test/cli/samples/dir-subdir/expected/Main.js
+++ b/test/cli/samples/dir-subdir/expected/Main.js
@@ -108,9 +108,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir-subdir/expected/widget/Widget.js b/test/cli/samples/dir-subdir/expected/widget/Widget.js
--- a/test/cli/samples/dir-subdir/expected/widget/Widget.js
+++ b/test/cli/samples/dir-subdir/expected/widget/Widget.js
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir/expected/Main.js b/test/cli/samples/dir/expected/Main.js
--- a/test/cli/samples/dir/expected/Main.js
+++ b/test/cli/samples/dir/expected/Main.js
@@ -108,9 +108,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/dir/expected/Widget.js b/test/cli/samples/dir/expected/Widget.js
--- a/test/cli/samples/dir/expected/Widget.js
+++ b/test/cli/samples/dir/expected/Widget.js
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/globals/expected/Main.js b/test/cli/samples/globals/expected/Main.js
--- a/test/cli/samples/globals/expected/Main.js
+++ b/test/cli/samples/globals/expected/Main.js
@@ -121,9 +121,12 @@ var Main = (function(answer) { "use strict";
 			var handler = handlers[i];
 
 			if (!handler.__calling) {
-				handler.__calling = true;
-				handler.call(this, data);
-				handler.__calling = false;
+				try {
+					handler.__calling = true;
+					handler.call(this, data);
+				} finally {
+					handler.__calling = false;
+				}
 			}
 		}
 	}
diff --git a/test/cli/samples/sourcemap-inline/expected/Main.js b/test/cli/samples/sourcemap-inline/expected/Main.js
--- a/test/cli/samples/sourcemap-inline/expected/Main.js
+++ b/test/cli/samples/sourcemap-inline/expected/Main.js
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
@@ -166,4 +169,4 @@ function callAll(fns) {
 	while (fns && fns.length) fns.shift()();
 }
 export default Main;
-//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vc3JjL01haW4uaHRtbCJdLCJzb3VyY2VzQ29udGVudCI6WyI8cD5IZWxsbyB3b3JsZCE8L3A+XG5cbjxzY3JpcHQ+XG5cdGV4cG9ydCBkZWZhdWx0IHtcblx0XHRvbnJlbmRlciAoKSB7XG5cdFx0XHRjb25zb2xlLmxvZyggJ2hlcmUnICk7XG5cdFx0fVxuXHR9O1xuPC9zY3JpcHQ+Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OyJ9
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWFpbi5qcyIsInNvdXJjZXMiOlsiLi4vc3JjL01haW4uaHRtbCJdLCJzb3VyY2VzQ29udGVudCI6WyI8cD5IZWxsbyB3b3JsZCE8L3A+XG5cbjxzY3JpcHQ+XG5cdGV4cG9ydCBkZWZhdWx0IHtcblx0XHRvbnJlbmRlciAoKSB7XG5cdFx0XHRjb25zb2xlLmxvZyggJ2hlcmUnICk7XG5cdFx0fVxuXHR9O1xuPC9zY3JpcHQ+Il0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OyJ9
diff --git a/test/cli/samples/sourcemap/expected/Main.js b/test/cli/samples/sourcemap/expected/Main.js
--- a/test/cli/samples/sourcemap/expected/Main.js
+++ b/test/cli/samples/sourcemap/expected/Main.js
@@ -100,9 +100,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/cli/samples/sourcemap/expected/Main.js.map b/test/cli/samples/sourcemap/expected/Main.js.map
--- a/test/cli/samples/sourcemap/expected/Main.js.map
+++ b/test/cli/samples/sourcemap/expected/Main.js.map
@@ -1 +1 @@
-{"version":3,"file":"Main.js","sources":["../src/Main.html"],"sourcesContent":["<p>Hello world!</p>\n\n<script>\n\texport default {\n\t\tonrender () {\n\t\t\tconsole.log( 'here' );\n\t\t}\n\t};\n</script>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
+{"version":3,"file":"Main.js","sources":["../src/Main.html"],"sourcesContent":["<p>Hello world!</p>\n\n<script>\n\texport default {\n\t\tonrender () {\n\t\t\tconsole.log( 'here' );\n\t\t}\n\t};\n</script>"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/test/cli/samples/store/expected/Main.js b/test/cli/samples/store/expected/Main.js
--- a/test/cli/samples/store/expected/Main.js
+++ b/test/cli/samples/store/expected/Main.js
@@ -120,9 +120,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/action/expected-bundle.js b/test/js/samples/action/expected-bundle.js
--- a/test/js/samples/action/expected-bundle.js
+++ b/test/js/samples/action/expected-bundle.js
@@ -44,9 +44,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/bind-width-height/expected-bundle.js b/test/js/samples/bind-width-height/expected-bundle.js
--- a/test/js/samples/bind-width-height/expected-bundle.js
+++ b/test/js/samples/bind-width-height/expected-bundle.js
@@ -76,9 +76,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js
--- a/test/js/samples/collapses-text-around-comments/expected-bundle.js
+++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js
@@ -52,9 +52,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/component-static-array/expected-bundle.js b/test/js/samples/component-static-array/expected-bundle.js
--- a/test/js/samples/component-static-array/expected-bundle.js
+++ b/test/js/samples/component-static-array/expected-bundle.js
@@ -32,9 +32,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/component-static-immutable/expected-bundle.js b/test/js/samples/component-static-immutable/expected-bundle.js
--- a/test/js/samples/component-static-immutable/expected-bundle.js
+++ b/test/js/samples/component-static-immutable/expected-bundle.js
@@ -36,9 +36,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/component-static-immutable2/expected-bundle.js b/test/js/samples/component-static-immutable2/expected-bundle.js
--- a/test/js/samples/component-static-immutable2/expected-bundle.js
+++ b/test/js/samples/component-static-immutable2/expected-bundle.js
@@ -36,9 +36,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/component-static/expected-bundle.js b/test/js/samples/component-static/expected-bundle.js
--- a/test/js/samples/component-static/expected-bundle.js
+++ b/test/js/samples/component-static/expected-bundle.js
@@ -32,9 +32,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js
--- a/test/js/samples/computed-collapsed-if/expected-bundle.js
+++ b/test/js/samples/computed-collapsed-if/expected-bundle.js
@@ -32,9 +32,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js
--- a/test/js/samples/css-media-query/expected-bundle.js
+++ b/test/js/samples/css-media-query/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
--- a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
@@ -44,9 +44,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/deconflict-builtins/expected-bundle.js b/test/js/samples/deconflict-builtins/expected-bundle.js
--- a/test/js/samples/deconflict-builtins/expected-bundle.js
+++ b/test/js/samples/deconflict-builtins/expected-bundle.js
@@ -62,9 +62,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/deconflict-globals/expected-bundle.js b/test/js/samples/deconflict-globals/expected-bundle.js
--- a/test/js/samples/deconflict-globals/expected-bundle.js
+++ b/test/js/samples/deconflict-globals/expected-bundle.js
@@ -37,9 +37,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/dev-warning-missing-data-computed/expected-bundle.js b/test/js/samples/dev-warning-missing-data-computed/expected-bundle.js
--- a/test/js/samples/dev-warning-missing-data-computed/expected-bundle.js
+++ b/test/js/samples/dev-warning-missing-data-computed/expected-bundle.js
@@ -65,9 +65,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/do-use-dataset/expected-bundle.js b/test/js/samples/do-use-dataset/expected-bundle.js
--- a/test/js/samples/do-use-dataset/expected-bundle.js
+++ b/test/js/samples/do-use-dataset/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js b/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js
--- a/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js
+++ b/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js
@@ -52,9 +52,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js b/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js
--- a/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js
+++ b/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js
@@ -52,9 +52,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js
--- a/test/js/samples/each-block-changed-check/expected-bundle.js
+++ b/test/js/samples/each-block-changed-check/expected-bundle.js
@@ -64,9 +64,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/each-block-keyed-animated/expected-bundle.js b/test/js/samples/each-block-keyed-animated/expected-bundle.js
--- a/test/js/samples/each-block-keyed-animated/expected-bundle.js
+++ b/test/js/samples/each-block-keyed-animated/expected-bundle.js
@@ -367,9 +367,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/each-block-keyed/expected-bundle.js b/test/js/samples/each-block-keyed/expected-bundle.js
--- a/test/js/samples/each-block-keyed/expected-bundle.js
+++ b/test/js/samples/each-block-keyed/expected-bundle.js
@@ -147,9 +147,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js
--- a/test/js/samples/event-handlers-custom/expected-bundle.js
+++ b/test/js/samples/event-handlers-custom/expected-bundle.js
@@ -44,9 +44,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/head-no-whitespace/expected-bundle.js b/test/js/samples/head-no-whitespace/expected-bundle.js
--- a/test/js/samples/head-no-whitespace/expected-bundle.js
+++ b/test/js/samples/head-no-whitespace/expected-bundle.js
@@ -44,9 +44,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js
--- a/test/js/samples/if-block-no-update/expected-bundle.js
+++ b/test/js/samples/if-block-no-update/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js
--- a/test/js/samples/if-block-simple/expected-bundle.js
+++ b/test/js/samples/if-block-simple/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
--- a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/inline-style-optimized-url/expected-bundle.js b/test/js/samples/inline-style-optimized-url/expected-bundle.js
--- a/test/js/samples/inline-style-optimized-url/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized-url/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/inline-style-optimized/expected-bundle.js b/test/js/samples/inline-style-optimized/expected-bundle.js
--- a/test/js/samples/inline-style-optimized/expected-bundle.js
+++ b/test/js/samples/inline-style-optimized/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/inline-style-unoptimized/expected-bundle.js b/test/js/samples/inline-style-unoptimized/expected-bundle.js
--- a/test/js/samples/inline-style-unoptimized/expected-bundle.js
+++ b/test/js/samples/inline-style-unoptimized/expected-bundle.js
@@ -48,9 +48,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/input-range/expected-bundle.js b/test/js/samples/input-range/expected-bundle.js
--- a/test/js/samples/input-range/expected-bundle.js
+++ b/test/js/samples/input-range/expected-bundle.js
@@ -60,9 +60,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/input-without-blowback-guard/expected-bundle.js b/test/js/samples/input-without-blowback-guard/expected-bundle.js
--- a/test/js/samples/input-without-blowback-guard/expected-bundle.js
+++ b/test/js/samples/input-without-blowback-guard/expected-bundle.js
@@ -56,9 +56,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js
--- a/test/js/samples/legacy-input-type/expected-bundle.js
+++ b/test/js/samples/legacy-input-type/expected-bundle.js
@@ -50,9 +50,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js
--- a/test/js/samples/media-bindings/expected-bundle.js
+++ b/test/js/samples/media-bindings/expected-bundle.js
@@ -60,9 +60,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js
--- a/test/js/samples/non-imported-component/expected-bundle.js
+++ b/test/js/samples/non-imported-component/expected-bundle.js
@@ -46,9 +46,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js
--- a/test/js/samples/setup-method/expected-bundle.js
+++ b/test/js/samples/setup-method/expected-bundle.js
@@ -32,9 +32,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/svg-title/expected-bundle.js b/test/js/samples/svg-title/expected-bundle.js
--- a/test/js/samples/svg-title/expected-bundle.js
+++ b/test/js/samples/svg-title/expected-bundle.js
@@ -52,9 +52,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/title/expected-bundle.js b/test/js/samples/title/expected-bundle.js
--- a/test/js/samples/title/expected-bundle.js
+++ b/test/js/samples/title/expected-bundle.js
@@ -32,9 +32,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js
--- a/test/js/samples/use-elements-as-anchors/expected-bundle.js
+++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js
@@ -56,9 +56,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/js/samples/window-binding-scroll/expected-bundle.js b/test/js/samples/window-binding-scroll/expected-bundle.js
--- a/test/js/samples/window-binding-scroll/expected-bundle.js
+++ b/test/js/samples/window-binding-scroll/expected-bundle.js
@@ -52,9 +52,12 @@ function fire(eventName, data) {
 		var handler = handlers[i];
 
 		if (!handler.__calling) {
-			handler.__calling = true;
-			handler.call(this, data);
-			handler.__calling = false;
+			try {
+				handler.__calling = true;
+				handler.call(this, data);
+			} finally {
+				handler.__calling = false;
+			}
 		}
 	}
 }
diff --git a/test/runtime/samples/component-events-fire-finally/_config.js b/test/runtime/samples/component-events-fire-finally/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-events-fire-finally/_config.js
@@ -0,0 +1,23 @@
+export default {
+	test(assert, component) {
+		const events = [];
+		component.on('foo', (shouldThrow) => {
+			events.push(shouldThrow);
+			if (shouldThrow) {
+				throw new Error();
+			}
+		});
+		component.fire('foo', false);
+		assert.equal(events.toString(), 'false');
+		let threw = false;
+		try {
+			component.fire('foo', true);
+		} catch (err) {
+			threw = true;
+		}
+		assert.equal(threw, true);
+		assert.equal(events.toString(), 'false,true');
+		component.fire('foo', false);
+		assert.equal(events.toString(), 'false,true,false');
+	},
+};
diff --git a/test/runtime/samples/component-events-fire-finally/main.html b/test/runtime/samples/component-events-fire-finally/main.html
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-events-fire-finally/main.html
@@ -0,0 +1 @@
+Foo