Unhandled Errors In RxJS Observable Streams Will Throw Errors In Angular 2 Beta 6
One of the most often cited problems with Promises is that if you don't explicitly handle an error condition in a Promise chain, it's easy to accidentally swallow said error, hiding it from both process and logging. RxJS Observable streams, on the other hand, seem to take a completely different approach. In an RxJS Observable stream, if you don't handle an error condition explicitly, RxJS will handle it implicitly by throwing your error. In Angular 2 Beta 6, the RxJS stream is being managed (so to speak) by the core Zone.js instance which means that these unhandled errors are subsequently caught by the Angular 2 default ExceptionHandler service.
Run this demo in my JavaScript Demos project on GitHub.
To explore this RxJS feature, I put together a tiny little demo that does nothing but create an "error stream" without an error handler (ie, a subscriber that doesn't listen for error events). I also added a few error handling and error conversion functions that you can see in the video:
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title> | |
Unhandled Errors In RxJS Observable Streams Will Throw Errors In Angular 2 Beta 6 | |
</title> | |
<link rel="stylesheet" type="text/css" href="./demo.css"></link> | |
</head> | |
<body> | |
<h1> | |
Unhandled Errors In RxJS Observable Streams Will Throw Errors In Angular 2 Beta 6 | |
</h1> | |
<my-app> | |
Loading... | |
</my-app> | |
<!-- Load demo scripts. --> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/6/es6-shim.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/6/Rx.umd.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/6/angular2-polyfills.min.js"></script> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/6/angular2-all.umd.js"></script> | |
<!-- AlmondJS - minimal implementation of RequireJS. --> | |
<script type="text/javascript" src="../../vendor/angularjs-2-beta/6/almond.js"></script> | |
<script type="text/javascript"> | |
// Defer bootstrapping until all of the components have been declared. | |
// -- | |
// NOTE: Not all components have to be required here since they will be | |
// implicitly required by other components. | |
requirejs( | |
[ /* Using require() for better readability. */ ], | |
function run() { | |
var App = require( "App" ); | |
ng.platform.browser.bootstrap( App ); | |
} | |
); | |
// --------------------------------------------------------------------------- // | |
// --------------------------------------------------------------------------- // | |
// I provide the root App component. | |
define( | |
"App", | |
function registerApp() { | |
// Configure the App component definition. | |
ng.core | |
.Component({ | |
selector: "my-app", | |
template: | |
` | |
<p> | |
<a (click)="createErrorStream()">Create error stream</a>. | |
</p> | |
` | |
}) | |
.Class({ | |
constructor: AppController | |
}) | |
; | |
return( AppController ); | |
// I control the App component. | |
function AppController() { | |
var vm = this; | |
// Expose the public methods. | |
vm.createErrorStream = createErrorStream; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
// I create an RxJS Observable error stream. | |
function createErrorStream() { | |
Rx.Observable | |
.throw( new Error( "Oops: I Did It Again." ) ) // I played with your heart, got lost in the game. | |
.subscribe( | |
function handleValue( value ) { | |
console.log( "handleValue:", value ); | |
} | |
// NOTE: Uncomment this to handle the upstream error | |
// and prevent the error from being thrown. | |
// -- | |
// , handleError | |
// Notice that we are not providing a CATCH handler in | |
// our subscriber configuration. As such, the underlying | |
// Subscriber class is automatically providing one for | |
// us, which basically implements: | |
// -- | |
// function handleError( e ) { throw( e ); } | |
) | |
; | |
// I log errors. | |
function handleError( error ) { | |
console.warn( "Caught an error in the stream." ); | |
console.log( error ); | |
} | |
} | |
} | |
} | |
); | |
</script> | |
</body> | |
</html> |
As you can see, we're using the .throw() operator to start an error stream; but, we're not catching or handling said error. So, when we run this code and click the link, we get the following console output:

As you can see, the unhandled error was rethrown (for lack of a better term) by RxJS. This thrown error was then caught by Zone.js and piped back into the Angular 2 context where it was subsequently logged by the ExceptionHandler service.
Coming from a Promise background, this behavior kind of caught me by surprise. But, I think this is a really nice feature of RxJS Observable streams. It means that unexpected error conditions will never get lost in an Angular 2 application. Well, not if you're using streams.
Want to use code from this post? Check out the license.
Reader Comments