How to handle "UnhandledPromiseRejectionWarning" in Fastify without "async / await" or ".catch" - node.js

I'm running a simple Fastify server and when I make a request and it fails, I wanna handle that exception in the setErrorHandler.
How can I achieve that?
This doesn't seem to work:
import fastify from 'fastify';
import fetch from 'node-fetch';
const app = fastify();
app.setErrorHandler(async (error, request, response) => {
// I want this to be called whenever there is an error
console.log('setErrorHandler');
return 'error';
});
app.get('/', (request, response) => {
// I do not want to use async / await or .catch here
fetch('https://...').then(() => { response.send(); });
});
I get the error UnhandledPromiseRejectionWarning and the server never sends a response.
I do NOT want to use async, await or .catch on the promise. The reason is I'm simulating a developer error. So if someone forgets to add a .catch and is not using async / await, I still wanna "catch" that error and return status 500 to the client.
I'm willing to change / wrap the request library adding a .catch in it, I just don't wanna change that endpoint handler code in general, since it's sort of out of my control (another developer might code it any way they want).

The reason is I'm simulating a developer error.
You can't manage these errors at runtime to reply to a request because you don't reference the reply object to act accordingly.
You can catch those errors with, but the reply cannot be fulfilled:
process.on('unhandledRejection', (err) => {
console.log(err)
})
As an alternative, I would set connectionTimeout, so the client will get a timeout error at least.
As you wrote, you already know that you should change the handler code to let Fastify be aware of the promise:
// add return
app.get('/', (request, response) => {
return fetch('https://...').then(() => { response.send(); });
})
For this reason, I think the solution to your problem should be taken offline adding a linter rule (like so) and integrate some Static Code Analysis in the CI pipeline to reject bad code.

Related

How to correctly create promise that resolves when webhook is called

I just watched James Snell's excellent talk on Promises in Node: https://www.youtube.com/watch?v=XV-u_Ow47s0
This got me thinking about a piece of code I have that calls a web service, supplying a webhook URL, which I'm sure is suboptimal. But I don't know how I should write this correctly. To paraphrase my code:
const activeRequests = new Map();
function makeRequest(id:string) {
return new Promise(resolve => {
fetch(`http://external.com/someComplexCalculation`,
{
method:"POST",
body: { webhook:`http://myservice.com/webhook/${id}` }
})
.then(res => {
activeRequests.set(id, resolve);
});
});
}
expressApp.post('/webhook/:id', (req, res) => {
const active = activeRequests.get(req.params.id);
if (active) {
active();
}
res.sendStatus(200);
});
I've skipped all the error handling for clarity, but you get the idea. Is this the best way to do this? The end goal is for the caller of makeRequest to receive a promise that will resolve when the webhook is called.
The expected flow is that my application calls await makeRequest(xx) which calls some external service - that service then calls back to my application via the /webhook/xx URL when it is done with its processing, at which point the original makeRequest promise resolves.
I currently store the resolve function from the new Promise in a Map since I can't call that resolve function until the webhook is invoked, but that is the crux of my question: is there a better way to achieve this that doesn't involve this odd arrangement?

How to catch async errors in nodejs Express outside request context?

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)
})
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)
})

Express middleware cannot trap errors thrown by async/await, but why?

These two middleware functions behave differently and I cannot figure out why:
Here, the error will get trapped by try/catch:
router.get('/force_async_error/0', async function (req, res, next) {
try{
await Promise.reject(new Error('my zoom 0'));
}
catch(err){
next(err);
}
});
But here, the error will not get trapped by try/catch:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
I looked into the Express source, and the handler looks like:
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
};
so why doesn't the try/catch there capture the thrown error?
I'm going to add an answer here even though you've already accepted another one because I think what's going on here can be explained better and this will help others attempting to understand this.
In your code here:
router.get('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
Let's discuss what is going on:
First, you declared the callback as async which you had to do in order to use await in it. An async function tells the interpreter to do several important things.
1. An async function always returns a promise. The resolved value of the promise will be whatever the function returns.
2. An async function is internally wrapped with a try/catch. If any exceptions are thrown in the top level scope of the function code, then those exceptions will be caught and will automatically reject the promise that the function returns.
3. An async function allows you to use await. This is an indicator to the interpreter that it should implement and allow the await syntax inside the function. This is tied to the previous two points above which is why you can't use await in just any 'ol function. Any uncaught rejections from await will also reject the promise that the function returns.
It's important to understand that while the async/await syntax allows you to kind of program with exceptions and try/catch like synchronous code, it isn't exactly the same thing. The function is still returning a promise immediately and uncaught exceptions in the function cause that promise to get rejected at some time later. They don't cause a synchronous exception to bubble up to the caller. So, the Express try/catch won't see a synchronous exception.
But here, the error will not get trapped by try/catch
I thought Express wrapped all middleware functions with try/catch, so I don't see how it would behave differently?
so why doesn't the try/catch [in Express] there capture the thrown error?
This is for two reasons:
The rejected promise is not a synchronous throw so there's no way for Express to catch it with a try/catch. The function just returns a rejected promise.
Express is not looking at the return value of the route handler callback at all (you can see that in the Express code you show). So, the fact that your async function returns a promise which is later rejected is just completely ignored by Express. It just does this fn(req, res, next); and does not pay attention to the returned promise. Thus the rejection of the promise falls on deaf ears.
There is a somewhat Express-like framework called Koa that uses promises a lot and does pay attention to returned promises and which would see your rejected promise. But, that's not what Express does.
If you wanted some Koa-type functionality in Express, you could implement it yourself. In order to leave other functionality undisturbed so it can work normally, I'll implement a new method called getAsync that does use promises:
router.getAsync = function(...args) {
let fn = args.pop();
// replace route with our own route wrapper
args.push(function(req, res, next) {
let p = fn(req, res, next);
// if it looks like a promise was returned here
if (p && typeof p.catch === "function") {
p.catch(err => {
next(err);
});
}
});
return router.get(...args);
}
You could then do this:
router.getAsync('/force_async_error/1', async function (req, res, next) {
await Promise.reject(new Error('my zoom 1'));
});
And, it would properly call next(err) with your error.
Or, your code could even just be this:
router.getAsync('/force_async_error/1', function (req, res, next) {
return Promise.reject(new Error('my zoom 1'));
});
P.S. In a full implementation, you'd probably make async versions of a bunch of the verbs and you'd implement it for middleware and you'd put it on the router prototype. But, this example is to show you how that could work, not to do a full implementation here.
This is because the call is asynchronous, take this code :
try {
console.log('Before setTimeout')
setTimeout(() => {
throw new Error('Oups')
})
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err)
}
console.log("Point of non-return, I can't handle anything anymore")
If you run it you should see that the error is triggered after Point of non-return.
When we're at the throw line it's too late, we're outside of try/catch. At this moment if an error is thrown it'll be uncaught.
You can work around this by using async/await in the caller (doesn't matter for the callee), ie :
void async function () {
try {
console.log('Before setTimeout')
await new Promise((resolve, reject) =>
setTimeout(() => {
reject(new Error('Oups'))
})
)
console.log('After setTimeout')
}
catch(err) {
console.log('Caught', err.stack)
}
console.log("Point of non-return, I can't handle anything anymore")
}()
Finally, this means that for Express to handle async errors you would need to change the code to :
async function handle(req, res, next) {
// [...]
try {
await fn(req, res, next); // shouldn't this trap the async/await error?
} catch (err) {
next(err);
}
}
A better workaround:
Define a wrap function like this :
const wrap = fn => (...args) => Promise
.resolve(fn(...args))
.catch(args[2])
And use it like this :
app.get('/', wrap(async () => {
await Promise.reject('It crashes!')
}))
Neither of these really answer the question, which if I understand correctly is:
Since the async/await syntax lets you handle rejected "awaits" with non-async style try/catch syntax, why doesn't a failed "await" get handled by Express' try/catch at the top level and turned into a 500 for you?
I believe the answer is that whatever function in the Express internals that calls you would also have to be declared with "async" and invoke your handler with "await" to enable async-catching try/catch to work at that level.
Wonder if there's a feature request for the Express team? All they'd need to add is two keywords in two places. If success, do nothing, if exception hand off to the error handling stack.
Beware that if you don't await or return the promise, it has nothing to do with express - it just crashes the whole process.
For a general solution for detached promise rejections:
https://stackoverflow.com/a/28709667
Copied from above answer:
process.on("unhandledRejection", function(reason, p){
console.log("Unhandled", reason, p); // log all your errors, "unsuppressing" them.
//throw reason; // optional, in case you want to treat these as errors
});

Node.js Express unhandledRejection removeListener

I have some error handling in my Express app, for async/await functionality, ie. trying to handle the uncaught errors centrally, to respond with appropriate status codes/messages.
I'm doing like so:
const handleRejection = (res, reason) => {
const { code, message } = reason
console.trace()
logger.error(reason)
// use `res` to send response to client
}
app.use((req, res, next) => {
process.on('unhandledRejection', handleRejection.bind(this, res))
next()
})
process.on('SIGTERM', () => process.removeListener('unhandledRejection', handleRejection))
This works fine for catching/handling errors, however, my logs are filled up each time an error is triggered. I don't believe this event listener, process.on('unhandledRejection'), is being removed correctly at all...
Is there a solution for this?
It seems that you're attaching a new event handler on every request.
Then, on SIGTERM you try to remove an event handler handleRejection which was never attached - you didn't attach handleRejection but handleRejection.bind(this, res) which returns a different function.
It looks like you may also be leaking memory by binding the function to every res object for every request.
This is a very strange way to handle errors. I'm not even sure that this is indeed what you are trying to do. Do you want to add so many event hendlers (one for every request made to your server) and then remove all of them on SIGTERM, when you try to exit your server?
Here is my solution for an Express middleware to pass unhandledRejection to main error middleware
// Handle unhandledRejection and pass error to next middleware
app.use(function (req, res, next) {
function unhandledRejection(reason, p) {
console.error('Possibly Unhandled Rejection at: Promise ', p, " reason: ", reason);
next(reason);
}
process.on('unhandledRejection', unhandledRejection);
// Manage to get information from the response too, just like Connect.logger does:
var end = res.end;
res.end = function (chunk, encoding) {
// Prevent MaxListener on process.events
process.removeListener('unhandledRejection', unhandledRejection);
res.end = end;
res.end(chunk, encoding);
};
next();
});

After res.send() event in Restify?

I'm trying to add Bugsnag to my Node Restify service. We have a ton of routes already and such so I'm trying not to add Bugsnag calls all over our code base and I'm also trying to do something global so there's never a mistake where a dev forgets to add the error reporting.
Conceptually I want after any res.send() to get the status code. If the statusCode is >=400 i want to notify Bugsnag by calling Bugsnag.notify. I already check for errors everywhere so no errors ever show up to the clients (browsers, phones, etc) but they do get sent, for example, res.send(401, { message: 'You dont have permission to do that' }) which I'd like to be able to hook into and pass who tried to do that, what route, etc. Problem is I can't get the after event to fire at all:
server.on('after', function (req, res, route, error) {
console.log('AFTER')
});
I think I misunderstand what after is for. It's at the top of my index.js before any routes or other middleware (server.use) is defined.
My general code structure looks something like:
server.post('/foo', function (req, res, next) {
FooPolicy.create(req, function (err) {
if (err) return res.send(err.code, err.data);
FooController.create(req.params, function (response) {
res.send(response.code, response.data)
next();
});
});
});
FooPolicy == checking permissions
FooController == actually creating the model/data
The issue is that the after event is currently treated like any other handler. That means that if you don't call next in every code path, the after event will never be emitted.
In the meantime, adding a next call will cause your after event handler to fire.
if (err) {
res.send(err.code, err.data);
next();
return;
}

Resources