UnhandledPromiseRejectionWarning Persists Even After Chaining a .catch() - node.js

My system consists of an Angular UI and a Node API. The UI submits a file to the API for processing, then gets the result back. This all works - however - the API sometimes fails at processing unexpected data.
I want to be able to catch the error(s) when they arise, stop execution so they won't screw up the UI, then send a message back to UI.
Here is my code so far:
const IncomingForm = require('formidable').IncomingForm;
asynch function myApi(req, res)
{
try // (1)
{
var form = new IncomingForm(...);
form.on('file', async(field, file) =>
{
const [result] = await sometimesBad(inParam); // (2) attach .catch(...);
...
res.send({goodFinal}); // execution should not reach here if error occurs before
});
form.on('end', ()=> {})
form.parse(req)
}
catch (erApi) // (3)
{
... // (4)
}
}
async function sometimesBad(x)
{
try // (5)
{
... lines of code could have run-time error depends on x ...
return goodResult;
}
catch(err2) // (6)
{
... // (7)
}
}
Currently, after hours of searching and trial and error, I:
am able to send a message back by chaining a .catch() at (2)
am unable to stop the execution via any combination of (1), (3), (4), (5), (6), (7), including the use of next(), throw new Error(), await Promise.reject(), return Promise.reject().
am always getting UnhandledPromiseRejectionWarning: Unhandled promise rejection.
Node version: 14.9
Update: In addition to accepted answer, there is no need to have (5), (6), (7).

In your code if (2) throws the error indeed is not handled. To handle it, you need to wrap the code inside async (field, file) => ... into try / catch, similar to how you did on the top level of middleware, and inside the catch you do next(error). Also add default error handler after all routes in your app. See How to return error to the client client without making node server crash? regarding that.

You can stop unhandledRejection(s) from crashing your app by handling them. However, if you fail to handle them using catch blocks, you can also watch for events on the process.
Code Example from the Docs:
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection at:', promise, 'reason:', reason);
// Application specific logging, throwing an error, or other logic here
});
somePromise.then((res) => {
return reportToUser(JSON.pasre(res)); // Note the typo (`pasre`)
}); // No `.catch()` or `.then()`
Alternatively, you can make your sometimesBad function return a Promise, which would cause all errors happening inside the Promise body to be thrown, which can then be handled in the catch block of the caller.

Related

Unhandled Promise Rejection: AxiosError: Network Error

Working on a MERN stack project. When trying to access the site on localhost on iPhone Posts are posts are not loading. Working fine on adnroid devices.
Screenshot of error
const fetchFeedPosts = async () => {
const URL = `${BASE__URL}api/posts/feed/${LOGGED__IN__USER}`;
await axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((e) => console.log(e.response));
}
fetchFeedPosts()
What the error means
When an Error is thrown (e.g. throw new Error()) it can
be catched locally (e.g. try{...} catch(err) { /** here */ })
or be passed on to the calling function. And in that fashion it bubbles up from caller to caller until it reaches a catch somewhere.
However, if it continues to bubble up, and there's nothing that captures it by the time it reaches the root of your application or code, then that will result in a so-called "Unhandled" error.
Now, as you may know, promises are more like jobs that drift around. They aren't called from the root of your application. But as they bubble up they can also reach a similar root-point, in which case they become "Unhandled Promise rejections".
What to do about it
Unhandled errors or rejections are bad practice though. Errors should be caught somewhere. And without catching them, you can't really know what has caused the error to happen in the first place.
In most cases, you can catch them with a .catch() function, (e.g. yourPromise.catch((err) => {console.err(err)}))
In case your promise is handled in an async function and waited for with an await keyword, then it's slightly different. In that case it makes more sense to use a try-catch block to capture your error.
How to apply it to your code
So, the first way of doing it would be to use the .catch() function
axios.get(URL)
.then((response) => setFeedPosts([...response.data].reverse()))
.catch((err) => console.error(err));
The alternative is to use the await syntax with a try-catch. If you want to use this syntax, you have to put the async keyword before your function.
try {
const response = await axios.get(URL)
setFeedPosts([...response.data].reverse()))
} catch (err) {
console.log(err);
}
Sure, you could mix the 2, but in most cases that would be rather strange.

How to handle errors using `.catch` in promises

I want to continue code execution when my promises do not have errors, If there are any errors however, I do not want to proceed. How do I gracefully handle this case ?
Eg.
/* start of REST call */
const resultA = fetchThings()
.then(things => returnSomeResult(things))
.then(...)
...
.catch(err => throw err); // cannot proceed computation if there is error
const resultB = fetchOtherThings()
.then(otherTings => returnOtherResult(otherThings))
.then(...)
...
.catch(err => throw err); // cannot proceed if there is error
const resultC = Promise.all([resultA, resultB]) => { // do other stuff }
// return resultC from REST call if successful
When an error occurs in either of the results, I am getting a UnhandledPromiseRejectionWarning on the terminal. It states that This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with a .catch(). I suspect it is because I am throwing the error inside the catch block.
However, I don't want the code to proceed when there is an error from either resultA or resultB.
How can I better handle this scenario of mine ?
My guess would be that you're not handling errors thrown by Promise.all(), which will be rejected if any of the promises it's handling will be rejected.
So you need to catch errors there as well:
Promise.all(…).then(…).catch(e => …)
FWIW, your catch()'s are superfluous:
.catch(err => throw err)
You're catching errors that were thrown and rethrowing them, which isn't necessary (they will be caught by .catch() that you'll attach to Promise.all()).

How to do proper error handling with async/await

I'm writing an API where I'm having a bit of trouble with the error handling. What I'm unsure about is whether the first code snippet is sufficient or if I should mix it with promises as in the second code snippet. Any help would be much appreciated!
try {
var decoded = jwt.verify(req.params.token, config.keys.secret);
var user = await models.user.findById(decoded.userId);
user.active = true;
await user.save();
res.status(201).json({user, 'stuff': decoded.jti});
} catch (error) {
next(error);
}
Second code snippet:
try {
var decoded = jwt.verify(req.params.token, config.keys.secret);
var user = models.user.findById(decoded.userId).then(() => {
}).catch((error) => {
});
user.active = true;
await user.save().then(() => {
}).catch((error) => {
})
res.status(201).json({user, 'stuff': decoded.jti});
} catch (error) {
next(error);
}
The answer is: it depends.
Catch every error
Makes sense if you want to react differently on every error.
e.g.:
try {
let decoded;
try {
decoded = jwt.verify(req.params.token, config.keys.secret);
} catch (error) {
return response
.status(401)
.json({ error: 'Unauthorized..' });
}
...
However, the code can get quite messy, and you'd want to split the error handling a bit differently (e.g.: do the JWT validation on some pre request hook and allow only valid requests to the handlers and/or do the findById and save part in a service, and throw once per operation).
You might want to throw a 404 if no entity was found with the given ID.
Catch all at once
If you want to react in the same way if a) or b) or c) goes wrong, then the first example looks just fine.
a) var decoded = jwt.verify(req.params.token, config.keys.secret);
b) var user = await models.user.findById(decoded.userId);
user.active = true;
c) await user.save();
res.status(201).json({user, 'stuff': decoded.jti});
I read some articles that suggested the need of a try/catch block for each request. Is there any truth to that?
No, that is not required. try/catch with await works conceptually like try/catch works with regular synchronous exceptions. If you just want to handle all errors in one place and want all your code to just abort to one error handler no matter where the error occurs and don't need to catch one specific error so you can do something special for that particular error, then a single try/catch is all you need.
But, if you need to handle one particular error specifically, perhaps even allowing the rest of the code to continue, then you may need a more local error handler which can be either a local try/catch or a .catch() on the local asynchronous operation that returns a promise.
or if I should mix it with promises as in the second code snippet.
The phrasing of this suggests that you may not quite understand what is going on with await because promises are involved in both your code blocks.
In both your code blocks models.user.findById(decoded.userId); returns a promise. You have two ways you can use that promise.
You can use await with it to "pause" the internal execution of the function until that promise resolves or rejects.
You can use .then() or .catch() to see when the promise resolves or rejects.
Both are using the promise returns from your models.user.findById(decoded.userId); function call. So, your phrasing would have been better to say "or if I should use a local .catch() handler on a specific promise rather than catching all the rejections in one place.
Doing this:
// skip second async operation if there's an error in the first one
async function someFunc() {
try {
let a = await someFunc():
let b = await someFunc2(a);
return b + something;
} catch(e) {
return "";
}
}
Is analogous to chaining your promise with one .catch() handler at the end:
// skip second async operation if there's an error in the first one
function someFunc() {
return someFunc().then(someFunc2).catch(e => "");
}
No matter which async function rejects, the same error handler is applied. If the first one rejects, the second one is not executed as flow goes directly to the error handler. This is perfectly fine IF that's how you want the flow to go when there's an error in the first asynchronous operation.
But, suppose you wanted an error in the first function to be turned into a default value so that the second asynchronous operation is always executed. Then, this flow of control would not be able to accomplish that. Instead, you'd have to capture the first error right at the source so you could supply the default value and continue processing with the second asynchronous operation:
// always run second async operation, supply default value if error in the first
async function someFunc() {
let a;
try {
a = await someFunc():
} catch(e) {
a = myDefaultValue;
}
try {
let b = await someFunc2(a);
return b + something;
} catch(e) {
return "";
}
}
Is analogous to chaining your promise with one .catch() handler at the end:
// always run second async operation, supply default value if error in the first
function someFunc() {
return someFunc()
.catch(err => myDefaultValue)
.then(someFunc2)
.catch(e => "");
}
Note: This is an example that never rejects the promise that someFunc() returns, but rather supplies a default value (empty string in this example) rather than reject to show you the different ways of handling errors in this function. That is certainly not required. In many cases, just returning the rejected promise is the right thing and that caller can then decide what to do with the rejection error.

Prevent "Unhandled promise rejection" error

In my server app I want to return a "forbidden" value when the user has no permissions for the endpoint.
To this end I create a rejected promise for reuse:
export const forbidden = Promise.reject(new Error('FORBIDDEN'))
and then elsewhere in the app:
import {forbidden} from './utils'
...
resolve: (root, {post}, {db, isCollab}) => {
if (!isCollab) return forbidden
return db.posts.set(post)
},
However, when I start my app I get the warning
(node:71620) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: FORBIDDEN
(node:71620) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
How can I tell Node that this Promise is fine to be unhandled?
I create a rejected promise for reuse
Well don't, it might be a lot easier to just create a function for reuse:
export function forbidden() { return Promise.reject(new Error('FORBIDDEN')); }
That will also get you an appropriate stack trace for the error every time you call it.
How can I tell Node that this Promise is fine to be unhandled?
Just handle it by doing nothing:
export const forbidden = Promise.reject(new Error('FORBIDDEN'));
forbidden.catch(err => { /* ignore */ }); // mark error as handled
(and don't forget to include the comment about the purpose of this seemingly no-op statement).
I wouldn't recommend using the return statement to provide an Error - this is ignoring the exact intention of throw!
Simply use:
if (!isCollab) throw new Error('FORBIDDEN');
If you don't want a stack trace there's no need to over-engineer - simply do:
if (!isCollab) throw 'FORBIDDEN';
If you need a message property to exist you can simply use:
if (!isCollab) throw { message: 'FORBIDDEN' };
(Note: I recommend against throwing anything other than an instance of Error! You'll regret it later when things break and you need to debug)
OP's usage is not completely described, but the OP's comment "BTW I didn't want to create a stack trace for every forbidden error because I don't want to leak details about my app. So I prefer to create the rejection only once." leads me to believe that at least part of the OP's motivation is to prevent info leakage from unhandled rejections of forbidden.
Returning a rejected (but defused) promise behaves differently in a sync vs. an async function. In the former the promise is returned verbatim. In the latter it is rewrapped in a promised and automatically rethrown (equivalent to throwing from inside the function). Whichever use was intended, it makes the program harder to understand.(Wrapping the Promise to be returned in an object or array would solve that problem).
Difference in behavior between sync and async funcs when returning forbidden :
async function test(){
try {
let a = await (async()=>{return forbidden;})();
} catch(e){console.log(e.message);} // outputs: 'FORBIDDEN'
try {
let a = (()=>{return forbidden;})();
// error undetected
} catch(e){console.log(e.message);} // never reaches here !!!
console.log("something else"); // outputs: something else
let a=(()=>{return forbidden;})(); // UHR + '#<Promise>' + no addr
console.log("something else"); // outputs: something else
await (async()=>{return forbidden;})(); // UHR + '#<Promise>' + no addr leak}
}
test();
Regardless of the the OP's usuage, leakage of program info from unhandled-rejections is a valid concern.
The below factory function makeError would provide a general solution, and it builds on the OP's original inspiration:
const verboten=new Error('verbotten');
const makeError = () => verboten;
async function test2(){
try {
await (async()=>{throw makeError();})();
} catch(e){console.log(e.message);} // outputs: 'verboten'
// uncomment the following to trigger UHR (unhandled rejection)
//await (async()=>{throw makeError();})(); // UHR + 'verboten' + no addr leak
}
Note that makeError returns the constant object verboten, rather than itself. (Yes, that is allowed, although it is rarely used.) So the stack trace is a fixed value, unrelated to the error location in the program, just like the original OP's forbidden.
That's fine for the release version, but a minor change to makeError could be made for a development version, where seeing the stack is useful:
const makeError = Error;

How to make node throw an error for UnhandledPromiseRejectionWarning

I'm using nock.back to mock out some API calls. When unexpected calls are made, UnhandledPromiseRejectionWarning gets printed to the console, but my tests pass, and those warnings are easily missed in the rest of the console output. I want an exception thrown instead of a silent error. How do I do this?
The way I use promises is:
function myFunction(){
return new Promise((resolve, reject) -> {
try {
// Logic
resolve(logic)
} catch(e) {
reject('Provide your error message here -> ' + e)
}
})
}
Or !
function myFunction().then( // Calls the function defined previously
logic => { // Instead of logic you can write any other suitable variable name for success
console.log('Success case')
},
error => {
console.log('myFunction() returned an error: ' + error)
}
)
UPD
Have you had a look here? https://nodejs.org/api/process.html#process_event_unhandledrejection
It describes an unhandledRejection event being emitted when you have no catch for a rejection from a Promise and provides the code to catch the WARNING and output it nicely to console.
(copy paste)
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason);
// application specific logging, throwing an error, or other logic here
});
Node.js runs on a single process.

Resources