1

I have a simple express server where a POST request kicks off a long-running job and returns a job ID. The server monitors the job status and the client can query for the job status over time. I do this with child_process.spawn, and have callbacks for the usual events on the child_process.

Sometimes an exception will happen during the job's execution, long after the initial "start job" request has returned. That calls my error callback, but then what? I can't throw an ApiError there, because express won't handle it -- I'll get an UnhandledPromiseRejectionWarning (which in a future node.js version will terminate the process).

Is there any way to set up a "global error handler" for express that would put a try/catch around the whole server?

A simple example would be something like this:

app.post('/testing', (req, res) => {
  setTimeout(() => { raise new ApiError('oops!') }, 1000)
})
GaryO
  • 5,873
  • 1
  • 36
  • 61
  • 1
    What is `raise`? Do you mean `throw`? – jfriend00 Feb 07 '20 at 18:35
  • Perhaps relevant: [Nodejs best practice exception handling](https://stackoverflow.com/questions/7310521/node-js-best-practice-exception-handling). The short answer is you design your code so you can properly catch all exceptions locally and not let them get up to some top level where it's too late to do anything useful with them other than log and shutdown. That snippet you show is not properly designed in that way. Also see [process Event: 'uncaughtException'](https://nodejs.org/api/process.html#process_event_uncaughtexception). – jfriend00 Feb 07 '20 at 18:39
  • In asynchronous code, wrapping everything in promises allows even asynchronous exceptions to properly propagate to a place where you can catch them locally. This is one of the huge natural advantages to designing asynchronous code with promises compared to plain callbacks. – jfriend00 Feb 07 '20 at 18:40
  • (Ha, yes, "throw" -- sorry abt that!) So the only proper thing is to have a try/catch inside the anon function that's the first arg to my `setTimeout` above? – GaryO Feb 07 '20 at 19:37
  • If you're going to use a plain asynchronous callback like that, then the only way to catch errors in it is to try/catch inside the callback. Or use `function delay(t) { return new Promise(resolve => { setTimeout(resolve, t)});}` instead and then you catch errors at a higher level `delay(1000).then(() => throw new ApiError('oops!')).catch(err => console.log("Got the error", err))` . Or put that in an `async` function and let it catch the rejected promise for you with `async function someFunc() {await delay(); throw new ApiError('oops!');}` and catch the error where you call the async function. – jfriend00 Feb 07 '20 at 20:06
  • Promises are your friend here. they make asynchronous error propagation automatic whereas plain asynchronous callbacks eat errors and don't propagate them. This is because both `async` functions and `.then()` handlers have automatic try/catch handlers built in that can convert exceptions into rejected promises which have a natural propagation path. Among other reasons, promises were added to the language to provide tools for this exact problem. – jfriend00 Feb 07 '20 at 20:07
  • You can simply use try-catch, after making the function async. – Random COSMOS Feb 11 '20 at 09:09

1 Answers1

2

straight from express docs,

"You must catch errors that occur in asynchronous code invoked by route handlers or middleware and pass them to Express for processing. For example:"

app.get('/testing', function (req, res, next) {
  setTimeout(function () {
    try {
      throw new Error('BROKEN')
    } catch (err) {
      next(err)
    }
  }, 100)
})
laxman
  • 1,781
  • 4
  • 14
  • 32
  • I don't think this works -- you can't call `next` outside the request context, can you? The reply has already been sent to the client by the time the timeout function runs. – GaryO Feb 11 '20 at 17:35