A quick post to note two important things when it comes to Node.js and Observables.
Unhandled exceptions kill the server
One of the paramount aspect of webservices on Node.js to remember is: any unhandled exceptions will blow up the webserver. It won’t just return ‘500’ to the user. It will, but also blow up the webserver.
This leads to 2 important things to consider:
- a webservice on Node.JS must be monitored by a Process Manager, and automatically relaunched when failing. This, of course, is ok with your service, but you designed it to fail-fast, in accordance to the 12-factor principles.
- you should handle as many errors as possible and do appropriate actions (logging, error-management notifying, replacement-value providing, etc.), so that only unforeseen exceptions will actually blow up the server. Which is actually part of Node.js philosophy: let unhandled exceptions kill the server, because if such exceptions occured, the server might be in a unstable state now; so better kill and respawn it.
Observables’ subscribe: the nuclear time-bomb in your code
It’s easy to deal with a received Observable and only care about the positive outcomes and “leaving for later” the handling of possible but “unlikely” errors the Observable might crash on.
obs.subscribe( result => { ... } );
If you wrote above code, you’re a punk, or you’re just unaware of the nuclear mole you just set up. You heard somewhere, especially in Node.js event-based programming: “always handle errors” but you didn’t handle possible errors in the subscribe. Mayyybe you got to the impression you were safe thanks to some larger-scope error-handling in your code (of course, you’re not a fool, you didn’t surround above subscribe with try… catch… knowing that the subscription itself can not fail), in such manner:
import { Observable, throwError } from "rxjs";
/**
* Returns a Promise which returns a result,
* only if this one matches some conditions.
* NOTE: tons of ways to do that better, this is
* only for the example
*/
async function showMustGoOn(): Promise<any> {
return new Promise((resolve, reject) => {
const obs: Observable<any> = throwError("You'll never get me if not in the subscribe error handler!");
obs.subscribe(
(result) => {
if (result.type === 5) { // some conditions...
resolve(result);
}
}
);
});
}
/**
* A function that could give the illusion that any error
* here or deeper will be caught and handled.
*/
async function supposedlyErrorShieldingFunction() {
// Bring it on, synchronous errors!
// It really does not make sense to do this here, because
// showMustGoOn is asynchronous (and we don't use 'await')
// and .then and .catch can't fail (their asynchronous callbacks can)
try {
const result: Promise<any> = showMustGoOn();
result.then(
(value) => { /*...*/ },
(err) => { /* I'll catch you here if that's what it takes! */ }
).catch(
(err) => { /* Otherwise, I'm totally cornering you here! */ }
);
} catch (e) {
/* There's basically no way you escape me now. As true as I'm a punk. */
}
}
supposedlyErrorShieldingFunction();
In above code, you’re trying to catch any error that bubbles up in the synchronous flow and within the returned Promise, but this does not address the rule: “handle all exceptions, especially in Observables and Promises” because the asynchronous error happening within the Observable is not handled. This error will bubble up unattended to the root of the process and make it crash.
It’s not a recommended way, but for the purpose of experiment, you could add this block of code anywhere:
// This catches all uncaught exception in the application
process.on('uncaughtException', error => {
console.log('uncaughtException', error);
});
/* // This would catch all unattended Promise rejection
process.on('unhandledRejection', (error: any) => {
console.log('unhandledRejection', error.message);
});
*/
which would print:
uncaughtException You'll never get me if not in the subscribe handler!
Those global catchers are not recommended, especially in production, because of above-mentionned philosophy: let unhandled exceptions kill the server, because if such exceptions occured, the server might be in a unstable state now; so better kill and respawn it.
