Home > Enterprise >  Is it possible to "bulletproof" a try..catch in async Javascript code?
Is it possible to "bulletproof" a try..catch in async Javascript code?

Time:02-19

Recently I've had a very frustrating time debugging a piece of async NodeJS code: an exception that I thought was definitely going to be caught in try..catch was leaking through, resulting in an unhandled promise error outside of the async_foo function.

async function async_foo() {
    try {
        await some_library.async_bar('some illegal argument');
    } catch (err) {
        console.error(err); // <- Whether this is called depends on async_bar's implementation !
    }
}

I've since learned that there are dozens of ways to shoot yourself in the foot in async JS because of how async..await is implemented via Promises, but still:

Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented? A library-based solution would count.

CodePudding user response:

Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented?

No, and there probably never will. The Node.js documentation explains it quite well:

By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where it left off", without leaking references, or creating some other sort of undefined brittle state. The safest way to respond to a thrown error is to shut down the process.

It's up to the library to account for every type of error and to safely handle them and propagate them (if necessary) to the consumer when they happen.

You could use the deprecated domain module but it probably wouldn't help you in this case because "no 'error' event is emitted for unhandled Promise rejections.".

CodePudding user response:

Is it possible to write your async JS code in a way that will absolutely definitely always handle nested async code's errors, regardless of how the nested async code is implemented? A library-based solution would count.

No, it is not. Poorly written asynchronous code can throw in a plain asynchronous callback that is called by the event loop from an empty stack frame with no ability for you to catch anything with a try/catch. Here's a trivial example:

setTimeout(() => {
    throw new Error("thrown from a setTimeout callback");
}, 10);

There is no way to catch that exception from outside the callback itself other than globally using something like:

process.on('uncaughtException', function(err) {
    // log and shut down
});

But at that point, you have no context for what really happened or how to fix the internal state. There could be files or sockets left open, there could be event handlers in place. There could be other resources allocated. There could be internal state left in a bad way. The usual advice at that point is to shut-down the server and restart.

In reality, you want to never get here. You want errors to be caught in context by the code that knows what to do to properly clean up and handle an error and, frankly, there really isn't any substitute for that.


Fortunately, properly written code using promises and either .then() and .catch() or await and try/catch will propagate rejected promises back up the call chain as far as you want it to go. But, to do that, the code has to be properly written. Poorly written code that doesn't chain or return promises can still create situations where exceptions or rejections aren't handled properly even when using promises.

  • Related