7

I am using a 3rd party package from npm, which in turn connects to some external API on IP address X.X.X.X and it crashes with the following error. The reason is clear, the network was down for a moment, and BOOM my entire program halts:

events.js:177
      throw er; // Unhandled 'error' event
      ^

Error: connect ENETUNREACH X.X.X.X:80
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1054:14)
Emitted 'error' event at:
    at Socket.socketErrorListener (_http_client.js:410:9)
    at Socket.emit (events.js:200:13)
    at emitErrorNT (internal/streams/destroy.js:91:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:59:3)
    at processTicksAndRejections (internal/process/task_queues.js:84:9) {
  errno: 'ENETUNREACH',
  code: 'ENETUNREACH',
  syscall: 'connect',
  address: 'X.X.X.X',
  port: 80
}

I am deliberately not saying which module on npm it is that causes the problem because I can't control what 3rd party module authors are doing. If I complained to them they may or may not fix it but my coding life needs to go on. Even if any code I call has a coding error, it shouldn't completely crash my calling script. There should be a way for me to catch the condition before it cramps my style completely.

What I have tried: I use error handling meticulously, try/catch around any 3rd party async library calls etc. Example:

var ThirdPartyModule = require("thirdPartyModule");
try {
   await ThirdPartyModule.doIt("some", "params");
} catch (err) {
   console.log("Ok so the module call failed. Let's try something else here, but don't abort my script please!");
}

The catch does does nothing. Every time the module is called and the connection error happens, it completely tanks my entire program with the above "throw er;".

My question is, what additional "wrapper" code can I write around my calls to the library to "catch" any crashes in code that isn't mine, so my code keeps executing?

I am still kind of a noob in nodejs, so I think I am missing some greater nodejs concept that's at work here. I come from C++ or Java so whenever something crashes in these languages, I can always catch() it so I am puzzled how the module can "escape" try/catch in my main script.

Note that I am not looking for answers like "You should make sure to always be connected to the Internet, that's what ENETUNREACH is about." Instead, I am trying to understand how to be the master of the "sub modules" that I am calling, i.e. catch their errors, or if I can't be their master, I want to understand why what I want is impossible in nodejs.

Thanks!

EDIT: in a similar question linked-to below, a commenter suggests to add a process.on("uncaughtException") handler. I will try this out now.

If this is the correct solution, please post it as an answer and explain the underlying concept. Why can't I just rely on try / catch which is what a reformed Java programmer would do? What did the author of the module on npm do to not pass the Error up the chain in his async function?

EDIT 2: Perhaps my question ends up being a duplicate of Catch all uncaughtException for Node js app - I would love an explanation though why some crash deep down must be caught globally like that, and doesn't percolate up the caller chain. Is it because the 3rd party coder made an error, and failed to transform all possible problems that could occur in his code, into proper throws in his async code?

EDIT 3: Ok I managed to track down the problem. The library that I used was not the culprit. It innocently called and promisified a 4th-party library (also on npm) which connects to a server via http.request(). Just that the author of that 4th-party library forgot to install a request.on('error') callback, so when the Internet connection has problems (never happens, right!), that becomes an unhandled situation. The 3rd party library never gets the expected callback "on error" from the 4th party library, and thus never manages to promisify that error situation. Instead, the error lingers unhandled in nodejs ether, crashing my entire script. So there...

blitter
  • 347
  • 5
  • 21
  • 1
    Does this answer your question? [How to catch a global error with NodeJS](https://stackoverflow.com/questions/34186146/how-to-catch-a-global-error-with-nodejs) – jmalenfant Dec 24 '19 at 02:22
  • Perhaps - thanks. One commenter suggests: process.on('uncaughtException', function(err) { console.log('Caught exception: ' + err); }); I believe my code is already doing that but I shall verify that it is really in effect here. It's a good starting point. – blitter Dec 24 '19 at 02:25
  • Think it won't `catch` anything because of the `await`. – Martin Zeitler Dec 24 '19 at 02:45
  • @MartinZeitler an `await` there should explicitly allow a `catch` - that's one of the advantages of writing it with an await. Any error thrown by the promise `await`ed should be thrown and catchable in a catch handler. – Joe Dec 24 '19 at 03:36
  • 1
    @blitter - it would be helpful at this point to know the specific library. Doing an `await` in a `try`/`catch` should work here **assuming the library deals in promises** but if the library you're using is just a regular thing that uses callbacks, you may need a different approach or to *promisify* the API. – Joe Dec 24 '19 at 03:38
  • 1
    @Joe thanks for the feedback. The library is documented as async safe, but apparently it isn't. It's pointless to expose the library or author here, could have happened to the best of us, but if I find the bug inside his code, I will let him know through the appropriate channels. – blitter Dec 24 '19 at 17:59

2 Answers2

6

Making absolutely no assumptions about the structure of the code inside the library, you're stuck with the below:

process.on('uncaughtException', function (err) {
    //do something
});

Reason being: If the library was using promises throughout, then the try/catch block would have caught it. It's very likely that inside the library the part throwing the error is inside a callback.

Best practice for async code should be to return an error to a callback, return a rejected Promise or throw inside a async block, and never throw inside functions using callbacks or raw Promises. However it seems extremely likely that is exactly what is happening. There is no way to handle the error gracefully at that point, only the catch-all uncaughtException.

Essentially in the case of a thrown error inside a Promise or a traditional async callback, the code is throwing a synchronous error and there is no synchronous try/catch block to handle it. And you can't inject one from outside the module.

So in short the error has no 'proper' mechanism to be handled. The third party module writers should be ashamed.

serakfalcon
  • 3,501
  • 1
  • 22
  • 33
  • 1
    "It's very likely that inside the library the part throwing the error is inside a callback" - what was the author supposed to do instead? Let's say he used a low-level connection module that crashed on "no Internet", and that modue is old-style callback-based. He should have promisifed the call, or should have chosen a Promise-based module in the first place? Am I correct? – blitter Dec 24 '19 at 13:28
  • if the library is using callbacks, you can promisify them. if the low-level connection module is throwing an error and it's synchronous, you put the offending function inside a try catch inside a promise. – serakfalcon Dec 24 '19 at 13:33
  • at some point Promises are just better behaved and structured callbacks so you can always convert. the key is you can't mix a synchronous throw error with an async flow, you have to pass the error back async using callbacks or Promises. e.g `await new Promise((res,rej)=>{try { maybeThrow(); return res();} catch (error) { return rej(error);}});` – serakfalcon Dec 24 '19 at 13:52
  • Thanks or the explanation @serakfalcon. Using this `await new Promise`, is there any considerations to make when calling this on an array of items using something like `for...of` ? I am using a third party library which seems to be causing the error you describe, but I am unsure how best to use wrap this in a `new Promise` inside a `for...of` loop – alexr89 Aug 01 '21 at 16:12
0

Try to chain it alike this:

await ThirdPartyModule.doIt("some", "params").catch(err => {
    console.log(err);
});

(that's untested code, since the ThirdPartyModule is unknown).

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • This is mixing async/await and Promise-chaining and therefore looks like it wouldn't work. In https://nodejs.dev/learn/error-handling-in-nodejs there's a mention that with async/await one *has to* use try/catch, which the OP is indeed already doing. – Thomas Ebert Nov 19 '20 at 23:28