RxJava: UndeliverableException when flatMap combined with switchMap - rx-kotlin

TL;DR
I think that flatMap combined with switchMap might not terminate the stream correctly, thus UndeliverableException happens. How can I fix this?
The Structure
I'm making a little bit complex stream -- combined with flatMap and switchMap -- like below, in RxKotlin (RxJava 3):
someObservable
.flatMapMaybe {
if (matchCondition(it)) Maybe.just(it)
else Maybe.never()
}.flatMapSingle {
procedureMiddle(it) // Inconsistent-time-consuming Single
}.switchMap {
procedureLater(it)
}.doOnError {
dealWithError(e)
}.retry()
.subscribeBy(
// ...
)
The procedureMiddle inside flatMapSingle has a chance of returning Error in the end.
The Exception
It turns out that sometimes the error from procedureMiddle might jump out of structure, not being ignored by retry, nor dealt in dealWithError in doOnError:
W/System.err: io.reactivex.rxjava3.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | my.custom.app.CustomException
// traces indicates that the Exception is thrown from inside procedureMiddle
The Question
In fact the UndeliverableException doesn't really cause crash, but it's a little bit annoying for me --especially it looks like a situation I need to handle. However I thought the structure is correctly written? So here's my question:
Does switchMap really (correctly) terminate the last stream from flatMap? (And can be used to prevent UndeliverableException?)
If so, at which part of code I should adjust? If not so, how could I prevent the exceptions along with my structure? (I want to concat procedureLater after procedureMiddle, and keep only latest one)
Any suggestion or explanation would be helpful.

The RxJava2 Official Doc explains a few about UndeliverableException:
UndeliverableException: wraps the original exception that can't be delivered due to lifecycle restrictions on a Subscriber / Observer.
So it, actually, does NOT mean that your code have any problems (directly). It just reminds you that some streams still throw error after you ended/canceled/disposed them.
About the question:
Does switchMap really (correctly) terminate the last stream from flatMap? (And can be used to prevent UndeliverableException?)
Yes, it terminates the last stream.
And NO. UndeliverableException would still happen. Because it terminates the last stream, so if the last stream throws error, the error would be wrapped and thrown as UndeliverableException.
If so, at which part of code I should adjust? If not so, how could I prevent the exceptions along with my structure?
You don't need to adjust the code, nor preventing UndeliverableException, as it always have a chance to come up when disposing/cancelling/ending any streams (manually or even automatically.)
I suggest just ignoring it:
RxJavaPlugins.setErrorHandler {
if (it is UndeliverableException) {
// This happens when throwable comes out AFTER subscription is canceled or disposed.
// Not what we care about as it is likely disposed by user or system.
Log.d(
"Application", "UndeliverableException: ignored.\n" +
"$it: problem ${it.cause} / ${it.cause?.message}\n" +
"at ${it.cause?.stackTraceToString()}"
)
return#setErrorHandler
}
// other Exceptions: do some report
}

Related

UWP Windows 10 app crashes in release mode but works fine in debug mode

My UWP app is crashing in Release mode and works fine in Debug mode but I can't put my finger on what the issue is but I know it's related to a combination of raising an event from System.Threading.Timer and MVVMLight.
I created an new dummy application and use the same code (ZXing.net.mobile where I used 2 portable libraries and I used my own user control which is a simplified version of theirs - I'm using events instead of <Action>). This works fine and I'm currently trying to put more steps into it i.e. include mvvmlight and navigation but so far, I can't reproduce the problem in this dummy app.
The error I'm getting is:
Unhandled exception at 0x58C1AF0B (mrt100_app.dll) in Company.MyApp.App.exe:
0xC0000602: A fail fast exception occurred. Exception handlers will not be
invoked and the process will be terminated immediately.
Followed by:
Unhandled exception at 0x0107D201 (SharedLibrary.dll) in
Company.MyApp.App.exe: 0x00001007.
When looking at the Threads window, one of the worker thread has the following information if that's of help.
Not Flagged > 4012 0 Worker Thread <No Name>
System.Private.Interop.dll!System.Runtime.InteropServices.
ExceptionHelpers.ReportUnhandledError Normal
[External Code]
System.Private.Interop.dll!System.Runtime.InteropServices.ExceptionHelpers.
ReportUnhandledError(System.Exception e) Line 885
System.Private.Interop.dll!Internal.Interop.InteropCallbacks.ReportUnhandledError
(System.Exception ex) Line 17
System.Private.WinRTInterop.CoreLib.dll!Internal.WinRT.Interop.WinRTCallbacks.
ReportUnhandledError(System.Exception ex) Line 274
System.Private.CoreLib.dll!System.RuntimeExceptionHelpers.ReportUnhandledException
(System.Exception exception) Line 152
System.Private.Threading.dll!System.Threading.Tasks.AwaitTaskContinuation.
ThrowAsyncIfNecessary(System.Exception exc) Line 784
System.Private.Threading.dll!System.Threading.WinRTSynchronizationContext.Invoker.
InvokeCore() Line 182
System.Private.Threading.dll!System.Threading.WinRTSynchronizationContext.Invoker.
Invoke(object thisObj) Line 162
System.Private.CoreLib.dll!System.Action<System.__Canon>.
InvokeOpenStaticThunk(System.__Canon obj)
System.Private.WinRTInterop.CoreLib.dll!Internal.WinRT.Interop.WinRTCallbacks.PostToCoreDispatcher.AnonymousMethod__0() Line 266
MyCompany.MyApp.App.exe
MyCompany.MyApp.App.ViewModels.ValidateHandler.Invoke(string pin)
MyCompany.MyApp.App.McgInterop.dll!McgInterop.ReverseComSharedStubs
.Proc_(object __this, System.IntPtr __methodPtr) Line 6163
MyCompany.MyApp.App.McgInterop.dll!Windows.UI.Core.DispatchedHandler__Impl.Vtbl.Invoke__STUB(System.IntPtr pComThis) Line 45147
[External Code]
Within my QR Code UserControl, it's using a System.Threading.Timer and it raises an event when a QR Code is found:
timerPreview = new Timer(async (state) =>
{
....
// Check if a result was found
if (result != null && !string.IsNullOrEmpty(result.Text))
{
Debug.WriteLine("Barcode Found: " + result.Text);
if (!this.ContinuousScanning)
{
delay = Timeout.Infinite;
await StopScanningAsync();
}
else
{
delay = this.ScanningOptions.DelayBetweenContinuousScans;
}
OnBarcodeFound(result.Text);
}
timerPreview.Change(delay, Timeout.Infinite);
}, null,
this.ScanningOptions.InitialDelayBeforeAnalyzingFrames,
Timeout.Infinite);
In the page that host the QRCode UserControl, I've got the following code:
private async void ScannerControl_BarcodeFound(string barcodeValue)
{
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
Debug.WriteLine(barcodeValue);
GetViewModel.BarcodeFound(barcodeValue);
});
}
As you can see, I'm passing the QrCode value to my ViewModel and from there, I sent a message and then re-direct to another page:
public void BarcodeFound(string barcodeData)
{
Messenger.Default.Send<string>(barcodeData, MessengerTokens.QrCodeFound);
this.NavigationFacade.NavigateToMyOtherPage();
}
I could keep going and provide additional code, but as I add additional breakpoints I can see that the code and passing the correct value and going to the correct location but eventually it throws this error. If I add additional error handlers or dispatcher code, it eventually works and goes to the next page as expected but when I click on a button in my CommandBar, it then takes a while and it eventually throws the same error, so by adding these additional bits of code, I feel I'm just pushing down the problem further down the line.
Anyone got any suggestions on how I get around this problem. I wish I could share the full app but definitely can't. So I know it will be hard to provide a solution, but if anyone has suggestions I'd appreciate them.
As I said, I think the issue is a result of a combination of threading and mvvmlight but it's driving me nuts that my test app so far is working exactly as expected, so I'm pretty sure it's not Zxing or my UserControl.
Any help, suggestions would be greatly appreciated or if you need me to provide additional info, please let me know what and I'll try to provide it.
Thanks.
Well, this was a painstaking exercise and a huge waste of my time!
It was down to a few things!!
I was using a different DataService in Releasemode which wasn't implemented. By implemented, I mean all my functions were returning the NotImplementedException or null values.
When passing my model to my ViewModel, I did not check if it was null, thus causing unhandled exceptions.
I had a chain of mvvmlight events (Messenger.Default.Send<>) being triggered and none were checking for error or null values.
While all of these were caused by poor validation from my part, these errors are extremely poorly reported in Release mode! if from the get go, I had received a NullReferenceException or any kind of exceptions, it would have put me in the right direction immediately, but throwing errors such as the one I've had were totally useless but it didn't but lesson learned!!
All I can say is that if this problem ever happens to you, don't waste your time rewriting code or trying to find workarounds. First work your way through your workflow/chain of events and hopefully, you'll eventually catch the culprit.
Hope this helps.
Sadly we were facing a similar issue, ours was involved with setting the qualifier values for changing localization on the fly in the app but came up with mystery fail fast/SharedLibrary native errors. Upgrading the Microsoft.NETCore.UniversalWindowPlatform package from 6.0.4 to 6.0.7 seems to have resolved the issue.
Only thought of this because another place I was researching this error involved someone solving a SharedLibrary problem by upgrading their NETCore package, that case was an earlier one (5.x), but figured it was worth a shot.

Handling errors at a global level

I am trying to understand how to build my error handling system for my api.
Let's say I have a the following line in a controller method :
var age = json.info.age;
with
json = {"id":1, "name":"John", info": {"age":27, "sex":"m"}}
Let's say that the object doesn't contain an info field, I'll get the following error TypeError: Cannot read property 'info' of undefined and my server will crash.
Is there a way to make a higher level abstraction and catch all the potential errors that I could have? Or should I have a try/catch system for each of the methods of my controllers?
BEWARE OF THE CODE BELOW, IT WILL BITE YOU WHENEVER IT CAN!
Don't use the code snippet below if you do not understand its
implications, please read the whole answer.
You can use the node way for uncaught errors. Add this in your config/bootstrap.js
Updated the snippet below to add what was said in the comments, also added a warning about using a global to respond to the user.
process.on('uncaughtException', function (err) {
// Handle your errors here
// global.__current__ is added via middleware
// Be aware that this is a bad practice,
// global.__current__ being a global, can change
// without advice, so you might end responding with
// serverError() to a different request than the one
// that originated the error if this one happened async
global.__current__.res.serverError();
})
Now, can doesn't mean should. It really depends on your needs, but do not try to catch BUGS in your code, try to catch at a controller level the issues that might not happen every time but are somehow expected, like a third-party service that responded with empty data, you should handle that in your controller. The uncaughtException is mainly for logging purposes, its better to let your app crash if there is a bug. Or you can do something more complicated (that might be better IMHO), which is to stop receiving requests, respond to the error 500 (or a custom one) to user that requested the faulty endpoint, and try to complete the other requests that do not relate to that controller, then log and shutdown the server. You will need several instances of sails running to avoid zero downtime, but that is material for another question. What you asked is how to get uncaught exceptions at a higher lvl than the controllers.
I suggest you read the node guide for error handling
Also read about domains, even thought they are deprecated you can use them, but you would have to deal with them per controller action, since sails does not provide any help with that.
I hope it helps.
You can check this way if you want to:
if (object != null && object.response != null && object.response.docs != null){
//Do your stuff here with your document
}
I don't really get what is your "object" variable in the first place, so i don't know if you can check it at a different level, is it a sails parameter to your controller ?
So that's how I did it, thanks to Zagen's answer.
module.exports.bootstrap = function(cb) {
process.on('uncaughtException', function (err) {
//Handle your errors here
logger.fatal(err);
global.__current__.res.serverError();
})
cb();
};
I send a generic error 500 to the user if any uncaught exception is thrown, and I log the error to the fatal level. On that way, my server is still accessible 24/7 and I can monitor the logs at another level and trigger an alarm on a fatal error. I can then fix the exception that was thrown.

Can the Azure Service Bus be delayed before retrying a message?

The Azure Service Bus supports a built-in retry mechanism which makes an abandoned message immediately visible for another read attempt. I'm trying to use this mechanism to handle some transient errors, but the message is made available immediately after being abandoned.
What I would like to do is make the message invisible for a period of time after it is abandoned, preferably based on an exponentially incrementing policy.
I've tried to set the ScheduledEnqueueTimeUtc property when abandoning the message, but it doesn't seem to have an effect:
var messagingFactory = MessagingFactory.CreateFromConnectionString(...);
var receiver = messagingFactory.CreateMessageReceiver("test-queue");
receiver.OnMessageAsync(async brokeredMessage =>
{
await brokeredMessage.AbandonAsync(
new Dictionary<string, object>
{
{ "ScheduledEnqueueTimeUtc", DateTime.UtcNow.AddSeconds(30) }
});
}
});
I've considered not abandoning the message at all and just letting the lock expire, but this would require having some way to influence how the MessageReceiver specifies the lock duration on a message, and I can't find anything in the API to let me change this value. In addition, it wouldn't be possible to read the delivery count of the message (and therefore make a decision for how long to wait for the next retry) until after the lock is already required.
Can the retry policy in the Message Bus be influenced in some way, or can a delay be artificially introduced in some other way?
Careful here because I think you are confusing the retry feature with the automatic Complete/Abandon mechanism for the OnMessage event-driven message handling. The built in retry mechanism comes into play when a call to the Service Bus fails. For example, if you call to set a message as complete and that fails, then the retry mechanism would kick in. If you are processing a message an exception occurs in your own code that will NOT trigger a retry through the retry feature. Your question doesn't get explicit on if the error is from your code or when attempting to contact the service bus.
If you are indeed after modifying the retry policy that occurs when an error occurs attempting to communicate with the service bus you can modify the RetryPolicy that is set on the MessageReciver itself. There is an RetryExponitial which is used by default, as well as an abstract RetryPolicy you can create your own from.
What I think you are after is more control over what happens when you get an exception doing your processing, and you want to push off working on that message. There are a few options:
When you create your message handler you can set up OnMessageOptions. One of the properties is "AutoComplete". By default this is set to true, which means as soon as processing for the message is completed the Complete method is called automatically. If an exception occurs then abandon is automatically called, which is what you are seeing. By setting the AutoComplete to false you required to call Complete on your own from within the message handler. Failing to do so will cause the message lock to eventually run out, which is one of the behaviors you are looking for.
So, you could write your handler so that if an exception occurs during your processing you simply do not call Complete. The message would then remain on the queue until it's lock runs out and then would become available again. The standard dead lettering mechanism applies and after x number of tries it will be put into the deadletter queue automatically.
A caution of handling this way is that any type of exception will be treated this way. You really need to think about what types of exceptions are doing this and if you really want to push off processing or not. For example, if you are calling a third party system during your processing and it gives you an exception you know is transient, great. If, however, it gives you an error that you know will be a big problem then you may decide to do something else in the system besides just bailing on the message.
You could also look at the "Defer" method. This method actually will then not allow that message to be processed off the queue unless it is specifically pulled by its sequence number. You're code would have to remember the sequence number value and pull it. This isn't quite what you described though.
Another option is you can move away from the OnMessage, Event-driven style of processing messages. While this is very helpful you don't get a lot of control over things. Instead hook up your own processing loop and handle the abandon/complete on your own. You'll also need to deal some of the threading/concurrent call management that the OnMessage pattern gives you. This can be more work but you have the ultimate in flexibility.
Finally, I believe the reason the call you made to AbandonAsync passing the properties you wanted to modify didn't work is that those properties are referring to Metadata properties on the method, not standard properties on BrokeredMessage.
I actually asked this same question last year (implementation aside) with the three approaches I could think of looking at the API. #ClemensVasters, who works on the SB team, responded that using Defer with some kind of re-receive is really the only way to control this precisely.
You can read my comment to his answer for a specific approach to doing it where I suggest using a secondary queue to store messages that indicate which primary messages have been deferred and need to be re-received from the main queue. Then you can control how long you wait by setting the ScheduledEnqueueTimeUtc on those secondary messages to control exactly how long you wait before you retry.
I ran into a similar issue where our order picking system is legacy and goes into maintenance mode each night.
Using the ideas in this article(https://markheath.net/post/defer-processing-azure-service-bus-message) I created a custom property to track how many times a message has been resubmitted and manually dead lettering the message after 10 tries. If the message is under 10 retries it clones the message increments the custom property and sets the en queue of the new message.
using Microsoft.Azure.ServiceBus;
public PickQueue()
{
queueClient = new QueueClient(QUEUE_CONN_STRING, QUEUE_NAME);
}
public async Task QueueMessageAsync(int OrderId)
{
string body = JsonConvert.SerializeObject(OrderId);
var message = new Message(Encoding.UTF8.GetBytes(body));
await queueClient.SendAsync(message);
}
public async Task ReQueueMessageAsync(Message message, DateTime utcEnqueueTime)
{
int resubmitCount = (int)(message.UserProperties["ResubmitCount"] ?? 0) + 1;
if (resubmitCount > 10)
{
await queueClient.DeadLetterAsync(message.SystemProperties.LockToken);
}
else
{
Message clone = message.Clone();
clone.UserProperties["ResubmitCount"] = ++resubmitCount;
await queueClient.ScheduleMessageAsync(message, utcEnqueueTime);
}
}
This question asks how to implement exponential backoff in Azure Functions. If you do not want to use the built-in RetryPolicy (only available when autoComplete = false), here's the solution I've been using:
public static async Task ExceptionHandler(IMessageSession MessageSession, string LockToken, int DeliveryCount)
{
if (DeliveryCount < Globals.MaxDeliveryCount)
{
var DelaySeconds = Math.Pow(Globals.ExponentialBackoff, DeliveryCount);
await Task.Delay(TimeSpan.FromSeconds(DelaySeconds));
await MessageSession.AbandonAsync(LockToken);
}
else
{
await MessageSession.DeadLetterAsync(LockToken);
}
}

Java exception: "Can't get a Writer while an OutputStream is already in use" when running xAgent

I am trying to implement Paul Calhoun's Apache FOP solution for creating PDF's from Xpages (from Notes In 9 #102). I am getting the following java exception when trying to run the xAgent that does the processing --> Can't get a Writer while an OutputStream is already in use
The only changes that I have done from Paul's code was to change the package name. I have isolated when the exception happens to the SSJS line: var jce: DominoXMLFO2PDF = new DominoXMLFO2PDF(); All that line does is instantiate the class, there is no custom constructor. I don't believe it is the code itself, but some configuration issue. The SSJS code is in the beforeRenderResponse event where it should be, I haven't changed anything on the xAgent.
I have copied the jar files from Paul's sample database to mine, I have verified that the build paths are the same between the two databases. Everything compiles fine (after I did all this.) This exception appears to be an xpages only exception.
Here's what's really going on with this error:
XPages are essentially servlets... everything that happens in an XPage is just layers on top of a servlet engine. There are basically two types of data that a servlet can send back to whatever is initiating the connection (e.g. a browser): text and binary.
An ordinary XPage sends text -- specifically, HTML. Some xAgents also send text, such as JSON or XML. In any of these scenarios, however, Domino uses a Java Writer to send the response content, because Writers are optimized for sending Character data.
When we need to send binary content, we use an OutputStream instead, because streams are optimized for sending generic byte data. So if we're sending PDF, DOC/XLS/PPT, images, etc., we need to use a stream, because we're sending binary data, not text.
The catch (as you'll soon see, that's a pun) is that we can only use one per response.
Once any HTTP client is told what the content type of a response is, it makes assumptions about how to process that content. So if you tell it to expect application/pdf, it's expecting to only receive binary data. Conversely, if you tell it to expect application/json, it's expecting to only receive character data. If the response includes any data that doesn't match the promised content type, that nearly always invalidates the entire response.
So Domino in its infinite wisdom protects us from making this mistake by only allowing us to send one or the other in a single request, and throws an exception if we disobey that rule.
Unfortunately... if there's any exception in our code when we're trying to send binary content, Domino wants to report that to the consumer... which tries to invoke the output writer to send HTML reporting that something went wrong. Except we already got a handle on the output stream, so Domino isn't allowed to get a handle on the output writer, because that would violate its own rule against only using one per response. This, in turn, throws the exception you reported, masking the exception that actually caused the problem (in your case, probably a ClassNotFoundException).
So how do we make sure that we see the real problem, and not this misdirection? We try:
try {
/*
* Move all your existing code here...
*/
} catch (e) {
print("Error generating dynamic PDF: " + e.toString());
} finally {
facesContext.responseComplete();
}
There are two reasons this is a preferred approach:
If something goes wrong with our code, we don't let Domino throw an exception about it. Instead, we log it (instead of using print to send it to the console and log, you could also toss it to OpenLog, or whatever your preferred logging mechanism happens to be). This means that Domino doesn't try to report the error to the user, because we've promised that we already reported it to ourselves.
By moving the crucial facesContext.responseComplete() call (which is what ultimately tells Domino not to send any content of its own) to the finally block, this ensures it will get executed. If we left it inside the try block, it would get skipped if an exception occurs, because we'd skip straight to the catch... so even though Domino isn't reporting our exception because we caught it, it still tries to invoke the response writer because we didn't tell it not to.
If you follow the above pattern, and something's wrong with your code, then the browser will receive an incomplete or corrupt file, but the log will tell you what went wrong, rather than reporting an error that has nothing to do with the root cause of the problem.
I almost deleted this question, but decided to answer it myself since there is very little out on google when you search for the exception.
The issue was in the xAgent, there is a line importPackage that was incorrect. Fixing this made everything work. The exception verbage: "Can't get a Writer while an OutputStream is already in use" is quite misleading. I don't know what else triggers this exception, but an alternative description would be "Java class ??yourClass?? not found"
If you found this question, then you likely have the same issue. I would ignore what the exception actually says, and check your package statements throughout your application. The java code will error on its own, but your SSJS that references the java will not error until runtime, focus on that code.
Update the response header after the body can solve this kind of problem, example :
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
response.getWriter().write("<html><body>...</body></html>");
response.setContentType("text/html");
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");

Handling exceptions in express

I'm having trouble understanding how to handle something that it seems like would be a pretty basic aspect of express. If I have some code that throws an exception in an async callback, there is no way I can catch that exception because the try/catch block is no longer in scope by the time the callback is running. In these scenarios the browser will hang until it eventually give up stating that the server is unresponsive. This is a very bad user experience. I would much rather be able to immediately return a 500 error to the client. The default express error handler apparently does not handle this situation. Here is some sample code:
var express = require("express");
var app = express();
app.use(app.router);
//express error handler (never called)
app.use(function(err, req, res, next) {
console.log(err);
res.send(500);
});
app.get("/test", function(req, res, next) {
require("fs").readFile("/some/file", function(err, data) {
a.b(); //blow up
});
});
app.listen(8888);
In the above code, the line a.b() throws a "ReferenceError: a is not defined" exception. The defined error handler is never called. Notice that the err object returned by fs.readFile() is null in this case because the file was correctly read. The bug is the code inside the async handler.
I have read this post about using node's uncaughtExpception even, but the documentation says not use that method. Even if I did use it, how would I then send the 500 response back to the user? The express response object is no longer around for me to use.
So how do you handle this scenario?
OK, I'm just going to post an entirely different answer since my first answer has some valuable information but is off topic in hindsight.
The short answer: the correct thing to do is what already happens: your program should print a stack trace and exit with an error.
The underlying thinking:
So I think you need to think about errors in different categories. My first answer deals with data-related errors that a well-written program can and should handle cleanly. What you are describing is a CRASH. If you read the node.js documentation you linked to, it is correct. The only useful thing your program can do at this point is exit with a stack trace and allow a process supervisor to restart it and attain an understood state. Once your program has crashed, it is essentially unrecoverable due to the extremely wide range of errors that could be the root cause of an exception getting to the top of the stack. In your specific example, this error is just going to continue happening every time until the source code bug is fixed and the application is redeployed. If you are worried that untested and buggy code is going to get into your application, adding more untested and buggy error handling code isn't really addressing the right problem.
But in short, no, there's no way to get a reference to the HTTP request object that caused this exception so AFAIK you cannot change the way this is perceived by the end user in the browser, outside of handling this at an intermediate reverse proxy layer where you could configure a crude timeout and send a more friendly error page (which of course would be useless for any request that isn't for a full HTML document).
The Bible of Error Handling in Node
Error Handling in Node.js by Dave Pacheco is the definitive work on this topic in my opinion. It is comprehensive, extensive, and thorough. I recommend reading and re-reading periodically.
To address #asparagino's comments, if an unhandled exception is easily reproducible or happening with high frequency, it's not an edge case, it's a bug. The correct thing to do is improve your code to not generate uncaught exceptions in face of this situation. Actually handle the condition, thus converting a programmer error into an operational error, and your program can continue without a restart and without an uncaught exception.
You should use express's error handling middleware via app.use(error, req, res, next). Express maintains a separate middleware stack that it uses when the normal middleware stack throws an uncaught exception. (Side note that express just looks at the callback's arity (number of expected arguments) to categorize it as regular middleware or error handling middleware, which is a bit magical, so just keep in mind you must declare the arguments as above for express to understand this is an error handling middleware).
And based on your question and comments, just understand that exceptions aren't all that useful in node.js because every async call gets a new stack, which is why callbacks are used everywhere and the 1st argument is an error universally. Your try/catch block in the example is only going to catch an exception thrown directly by findById (like if id were undefined, as it is in your snippet), but once an actual call to the database is made, that's it, the call stack is over and no further exceptions can happen until a totally different call stack starts when node invokes the async IO callback.
Thanks for the answer, but this only works if I put the try/catch inside the async callback and have the catch do next(exp). I'd like to avoid having separate try/catch blocks in every single async callback.
Nope, that's not true. You don't have to manually call next(exp). Express will catch the error and trigger the error handling middleware for you (that's how express does it's developer-friendly exception report pages in dev mode anyway). And async libraries don't throw exceptions even under "normal" error conditions. They pass an error to the callback, so in general you don't have to bother with try/catch that much in node. Just never ignore an error parameter passed to a callback function, and you're fine.
You don't see this boilerplate in node:
someDb.query(someCriteria, function (error, result) {
try {
//some code to deal with result
} catch (exception) {
callback(exception);
}
});
You do see this though:
someDb.query(someCriteria, function (error, result) {
if (error) {
callback(error);
return;
}
//some code to deal with result
});
Node handles IO differently, which means the call stack works differently, which means exceptions work differently, which means error handling works differently. You can write a stable node/express app that handles errors without crashing without writing a single try/catch. (express has one that handles uncaught errors that bubble all the way to the top). It's not a functional limitation, it's just a consquence of async IO that means you have to write your code differently and handle errors with callbacks instead of exceptions. Thinking of it as a "limitation" as opposed to the "way it is" is putting a negative connotation on something that is really just a technical reality. There are clean and robust patterns for exception handling in both a synchronous and asynchronous paradigm.

Resources