In Express.js, how do you handle asynchronous errors within route handlers when not using `async/await`, and why is this handling critical?
When not using `async/await`, asynchronous errors in Express.js route handlers, typically arising from callback-based asynchronous functions (e.g., database queries or file system operations), must be explicitly caught and passed to the `next()` function. This is critical because uncaught errors within asynchronous operations will not be automatically caught by Express's default error handling middleware. This can lead to unhandled exceptions, causing the Node.js process to crash or, at the very least, leave requests hanging indefinitely. The correct approach involves catching any errors within the callback function and then calling `next(err)` to pass the error to Express's error handling middleware. Express's error handling middleware is a special type of middleware function that takes four arguments: `(err, req, res, next)`. When `next(err)` is called, Express skips any regular middleware and routes and passes the error to the first error handling middleware it finds. Example: `app.get('/data', function(req, res, next) { someAsyncOperation(function(err, data) { if (err) { return next(err); } res.json(data); }); }); app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); });` Without explicitly handling asynchronous errors in this way, Express will be unaware of the error, and the request will never be properly handled, potentially leading to a server crash or a hung request, which severely impacts application stability and reliability.