Is there any way to catch an error that occurs in an async callback after an Express next()
or res.send()
has been called from middleware or a route handler? Consider the following code:
app.use('/throw-error', (req, res) => {
setTimeout(() => {
throw new Error('Async error causes thread death')
}, 500)
res.send('This thread is going to die...')
})
It will execute and send "This thread is going to die..." to the browser. It will also, a half second later, crash that Node thread it is running in. If you happen to be running an app that uses Node's cluster
module, maybe it launches a new thread, but it died nonetheless. You might see something like this in your logs:
::1 [2019-07-17T18:54:55.142Z] - [71700] 4.740 ms "GET /throw-error" 200 -
/Users/moryl/Projects/crashtest/express.js:66
throw new Error('Async error causes thread death')
^
Error: Async error causes thread death
at Timeout.setTimeout [as _onTimeout] (/Users/moryl/Projects/InSight/sources/server/config/express.js:66:13)
at ontimeout (timers.js:436:11)
at tryOnTimeout (timers.js:300:5)
at listOnTimeout (timers.js:263:5)
at Timer.processTimers (timers.js:223:10)
That thread is now dead.
My question is, how the heck do you handle a (possibly unknown) async error that is outside the scope of a normal request, whether by design or through bad code? How do I prevent the thread from dying?
I don't want to be told that I shouldn't do this kind of stuff in async calls to begin with. I know this. I'm trying to write defensive code to catch "bad stuff" written by others.
This has been documented in express
error handling doc:
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('/', function (req, res, next) { setTimeout(function () { try { throw new Error('BROKEN') } catch (err) { next(err) } }, 100) })
The above example uses a try...catch block to catch errors in the asynchronous code and pass them to Express. If the try...catch block were omitted, Express would not catch the error since it is not part of the synchronous handler code.
So, basically you need to try..catch
the route. (the examples are basically same, mother of coincidence)
My question is, how the heck do you handle an async error that is outside the scope of a normal request, whether by design ...
You still want to handle errors in asynchronous code, even if it was fired and forgotten by design. Add a try { } catch { }
or .catch
to every independent task. With asynchronous code, Promises and async
/ await
help you (as they group independent callbacks into tasks, and you can handle errors per task then):
const timer = ms => new Promise(res => setTimeout(res, ms));
async function fireAndForgetThis() {
await timer(500);
throw new Error('Async error doesn't cause thread death, because its handled properly')
}
fireAndForgetThis()
.catch(console.error); // But always "handle" errors
... or through bad code?
Fix bad code.
How do I prevent the thread from dying?
That's not the thing you want to prevent. If an error occurs, and was not handled, your application gets into an unplaned state. Continuing execution might create even more problems. You don't want that. You want to prevent the unhandled rejection / unhandled error itself (by handling it properly).
For sure there are cases you can't handle, e.g. if the connection to the backing database goes down. In that case, NodeJS crashes, causes the monitoring to wake up DevOps that get the database back running. Crashing is also a form of handling the error ;)
If you read this far, and you still want to handle unhandled errors, don't. Okay well, you probably have your reasons, there you go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With