Node process exits even after error handling - node.js

I have something like this written in nodejs
const someOtherOperation = async (message) => {
try {
await doSomeIoOperation(message);
} catch (err) {
something
throw Error("Error doing someOtherOperation");
} finally {
await someCleanup();
}
}
const someOperation = async (message) => {
// something else
await someOtherOperation(message);
// something else
}
const main = async () => {
let messagePromises = []
let messages = await getMessages(); // fetching message from a message broker
for (let message of messages) {
messagePromises.push({ id: message.id, promise: someOperation(message) });
}
for (let messagePromise of messagePromises) {
try {
await messagePromise.promise;
} catch (err) {
console.log(err);
}
}
}
The expected behaviour is the for loop with try catch should not end even if there is a error in one of the promises.
What is happening is my process is ending abruptly when i get an error in someotherOperation method , i do not understand i have a try catch at the main loop and any error propagating from the innermost function should be caught in the for loop in main function but it isn't getting caught somehow and the function just ends abruptly

Node.js detection of unhandled rejection is incorrect. There are specific spots in the life cycle of a rejected promise where the engine checks to see if there's a handler and it does not always wait until the last possible moment so it can miss places that we add a handler.
In my code the place where i create a bunch of promises without any error/rejection handlers
messagePromises.push({ id: message.id, promise: someOperation(message) });
when i do the first await on the first promise it return rejected but during this time consider other promises have also been rejected , the Nodejs engine checks if there is a handler for these rejected promises and throws and error if no handler is present. So even though i add a try catch for these promises in a hope to handle the rejections the nodejs engine has already run a check for a handler and not getting one decided to throw a unhandled promise rejection.
To get around my style of code what i did was
const main = async () => {
let messagePromises = []
let messages = await getMessages(); // fetching message from a message broker
for (let message of messages) {
messagePromises.push({ id: message.id,
promise: someOperation(message).then(data=>[data,null]).catch(err=>[null,err]);
}
for (let messagePromise of messagePromises) {
const [data,err] = await messagePromise .promise;
if(err) {
//some error handling code here.
}
}
}
This is a pretty weird behaviour for such a mature language.

Related

Try catch block doesn`t catch error in node.js

class AuthController {
static methods = {
GET: {
'/auth/signup': {
func: AuthService.signUp,
response: (data, res) => {
res.statusCode = 200;
res.end(JSON.stringify(data));
},
},
},
};
static use(req, res) {
const route = this.methods[req.method][req.url];
if (!route) {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Not found 404!' }));
return;
}
try {
const data = JSON.parse(req?.body?.data || '{}');
const result = route.func({ ...data });
route.response(result, res);
} catch (err) {
console.log(err, 'here');
res.statusCode = err.statusCode || 500;
res.end(JSON.stringify(err.message));
}
}
}
class AuthService {
static async signUp({ login, password }) {
if (!login || !password) throw new BaseError(400, 'kl', 'Custom error');
}
}
It shows the error in console but try catch block doesn't see it.
Here is the traceback.
I don't know what the reason is because the function which throws error is inside of the block. Help please!
The trace back that you attached tells you exactly what the problem is and what you need to do:
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 .catch()
You can't catch an exception thrown by an async function with a try..catch block outside of that function, because script execution reaches the catch block before the async execution is finished. You therefor have to use .catch(..) instead:
const result = route.func({ ...data }).catch((err) => {
console.log("catched error: ", err);
});
I see one issue. You have declared signUp() to be async. That means it always returns a promise and it means that any throw operations inside it reject that promise that it returns (the exception doesn't propagate synchronously). But, when you attempt to call it here:
const result = route.func({ ...data });
You don't await it so when signUp() rejects, the promise goes into result, but nobody ever handles the fact that the promise rejected and you get UnhandlePromiseRejectionWarning from the system.
I can't see the whole overall design (of all the other routes), but perhaps you just need to add await to this:
const result = await route.func({ ...data });
And, you would have to make .use() be async also.
Or, if signUp() doesn't actually need to be async, then just remove the async from its declaration and the throw will be synchronous (instead of being turned into a rejected promise) and your try/catch will catch it then.

How to handle error in async/await function?

I have a function like this:
async getPatient(patientId: string): Promise<PatientDTO> {
const patient = await PatientDAO.getPatients({ id: patientId })
if (patient.length === 0) {
throw new NotFoundError("Patient Not Found!")
}
return patient[0]
}
But I got an error
UnhandledPromiseRejectionWarning: Error: Patient Not Found!
This happened cause I have used async function. How can I make this code running properly?
In order to manage errors in an async function, you have to use a try/catch block:
async getPatient(patientId: string): Promise<PatientDTO> {
try {
const patient = await PatientDAO.getPatients({ id: patientId })
return patient[0]
} catch (error) {
// Do whatever you may want with error
throw error;
}
}
I should mention, that if you simply want to throw the error thats received from getPatients theres no need for a try/catch block at all. Its only needed if you wish to modify the error or perform an extra action according to the error that was thrown.
You have 2 options:
First one is try/catch block with await keyword. Please notice that await has to be used in async function.
try {
const patient = await getPatient(foo);
// handle your data here
} catch(e) {
// error handling here
}
Second one is catch function
getPatient(foo)
.then(patient => {
// handle your data here
}).catch(error => {
// error handling here
});

Why doesn't multilayered async functions catch error thrown at lowest level in node?

I'm trying to test a failure mode of some mailing code which at the lowest level may throw an error. All the layers between the test and the function which throws are all async and use await on the functions below them. At the top level (also in an async function I have a try catch block. However node is throwing an unhandled promise exception before the error propages to this level.
My test code looks like this
beforeEach(function() {
//set default values - tests can change them
this.reasons = '';
this.reschedules = 0;
this.params.cid = 35124;
this.startTest = async () => {
/* this.confirmation is an async function under test,
this.mailer is a mock mailer with an async "send" method
which will throw an error in the correct test */
const doner = this.confirmation(this.mailer);
// ..other actions related to mocking database access made by confirmation
await doner;
return this.mailer.maildata; //provide info on parameters passed to this.mailer
};
});
it('Failure to send is reported', async function() {
this.mailer.sendResolve = false; //tell mock mailer to fail send request
try {
await this.startTest();
expect(true).to.be.false;
} catch(err) {
expect(err).to.be.instanceOf(Error);
}
});
the mock mailer is a bit like this
class Mailer {
constructor(user,params){
...
}
...
async send(subject, to, cc, bcc) {
this.maildata.subject = subject;
if (to !== undefined) this.maildata.to = to;
if (cc !== undefined) this.maildata.cc = cc;
if (bcc !== undefined) this.maildata.bcc = bcc;
if (!this.sendResolve) throw new Error('Test Error');
}
...
}
and a summary of the code under test
module.exports = async function(mailer) {
//get confirm data from database
const cData = await confirm(mailer.params.cid, mailer.db);
if (cData.count > 0) {
// ... format the email message and build it into maildata
await mailer.send(
subject,
emailAddress,
null,
process.env.PAS_MAIL_FROM,
{
pid:cData.pid,
type: 'confirmation',
extra: `Calendar ID ${mailer.params.cid} with procedure ${cData.procedure}`
}
);
debug('message sent, update the database');
await mailer.db.exec(async connection => {
...
});
debug('success');
} else {
debug('invalid calendarid');
throw new Error('Invalid Calendar ID');
}
};
As can be seen the call path from the async send function which throws back up the stack to the try {}catch(){} are all async functions. But when I run this test node outputs an unhandled promise rejection.
I've tried using the visual studio code debugger to single step through this, I get a bit lost caught in the machinery which wraps async functions to turn them into promises providers. As far as I can see, one layer of error is handled correctly and then fails at the next layer up.
Does this mean that every async function must have a try catch block to catch and rethrow any error? I can't find any explanation that says I have to do that.
To answer your question:
Does this mean that every async function must have a try catch block to catch and rethrow any error?
Errors propogate up through await-ed calls like you expected:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
await inner();
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === 'something bad happened'); // Success!
});
...so no, you don't need a try / catch block at every level.
Tracking down unhandled Promise rejections
I can't see exactly where the await chain might be broken in the code from your sample, but to help track down unhandled Promise rejections you can add a process handler for the unhandledRejection event and look at the logged Promise to see where the rejection began and track backwards through the call stack from there:
const assert = require('assert');
const outer = async () => {
await middle();
}
const middle = async () => {
inner(); // <= this will cause an Unhandled Rejection
}
const inner = async () => {
throw new Error('something bad happened');
}
it('should catch the error', async () => {
let errorMessage;
try {
await outer();
}
catch (err) {
errorMessage = err.message;
}
assert(errorMessage === undefined); // Success! (broken await chain)
})
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p);
console.log('reason:', reason);
});
...which in this case logs:
Unhandled Rejection at: Promise {
<rejected> Error: something bad happened
at inner (.../code.test.js:12:9)
at inner (.../code.test.js:8:3)
at middle (.../code.test.js:4:9) // <= this is the broken link
at Context.outer (.../code.test.js:18:11)
at callFn (...\node_modules\mocha\lib\runnable.js:387:21)
...
...which points us to the Error thrown in inner, and by tracing up the chain we find middle to be the broken link.

how to handle errors in multiple async await whle promise rejected without using try catch blocks?

i am new to express and was making user signup api. In this i need to hash user password and generate a random string. these both things are done by aync/await.If one promise got rejected then it returns the response in error handling but shows me warning for unhandled rejected promise.
methods.signup = async (req,res,next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(config.errorCodes.validation).json({errors:errors.array()});
}
let payload = req.body;
var newUser = new User({
username:payload.username,
fullname:payload.fullname,
email: payload.email,
password: payload.password,
urltoken:{
token:null
},
actStatus: 0,
provider: 'email',
role:1
});
let hash = commonMethods.generatePasswordHash(payload.password);
let token = commonMethods.createRandomString();
let [a,b] = await Promise.all([hash,token]).catch(err=>{
res.status(400).json({status:false,msg:"error in promise"})
});
newUser.password = a;
newUser.urltoken.token = b;
newUser.save(function(err){
if(err) {
return res.status(config.errorCodes.success).json({ status:false
,msg:"Error in saving userdata. Please try again",err});
}
else {
commonMethods.sendMail(newUser);
return res.status(config.errorCodes.success).json({"status":true,
"msg":"Registered succesfully. Please Check your Email to verify
your account.",newUser})
}
});
}
and promise are -
commonMethods.createRandomString = function(password){
return new Promise(function(resolve, reject) {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var string_length = 25;
var randomstring = '';
for (var i=0; i<string_length; i++) {
var rnum = Math.floor(Math.random() * chars.length);
randomstring += chars.substring(rnum,rnum+1);
}
reject(randomstring);
// resolve(randomstring);
})
}
which is always rejected for creating error.
Here is get below error
UnhandledPromiseRejectionWarning: Unhandled promise rejection. 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 .catch(). (rejection id: 2)
(node:15172) [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 to catch these errors without using try catch or then for promises and keeping my code simple and easy to read.
The problem is the lines
let [a,b] = await Promise.all([hash,token]).catch(err=>{
res.status(400).json({status:false,msg:"error in promise"})
});
If either hash or token throw, then the .catch function is entered, but the catch function doesn't return anything: so, after the await and catch have finished, the interpreter will see something like
let [a,b] = undefined
which won't work (undefined is not iterable), so the whole .signup method rejects. You need some way for the rest of the signup function to know to stop executing if there was an error in hash or token, maybe something like
const result = await Promise.all([hash,token]).catch(err=>{
res.status(400).json({status:false,msg:"error in promise"})
});
if (!result) return;
let [a,b] = result;
You say you don't want to use try/catch, but the logic might be a bit easier to follow that way, since you could return immediately from inside the catch:
let a, b;
try {
([a, b] = await Promise.all([hash,token]));
} catch(e) {
return res.status(400).json({status:false,msg:"error in promise"});
}
Coding Convention Suggestion for API
1) There are two ways to handle errors neatly. The first one is by wrapping all the async routes with a try catch block which will handle both synchronous and asynchronous code errors.
Async/Await
methods.foo = async(req, res, next) => {
try {
// assuming bizz is a utility analogous to createRandomString
// and returns a promise object which will either get resolved or rejected
// if resolved the response will end in this case and end the response
// if rejected, await will throw an error which will be caught by the catch block
var bar = await bizz(); // Asynchronous code(which will wait for a promise to be resolved)
// {... DO SYNCHRONOUS STUFF ...}
// if suppose there is an error in your synchronous code,
// then it will throw an error which also will be caught by the catch block
res.end();
} catch (err) {
// {... DO WHAT YOU NEED TO WITH THE ERROR CAUGHT BY EITHER Asynchronous OR Synchronous part of the method ...}
console.log(err);
res.end(err);
}
}
2) The second way is to have a middleware that wraps all the routes so that rewriting try catch for all the routes is avoided. Here both synchronous and asynchronous errors will be handled by the .catch() part in the async Middleware.
Using Async Await Middleware
const asyncMiddleware = (asyncFunction) => {
return (req, res, next) => {
Promise.resolve(asyncFunction(req, res, next))
.catch((err) => {
// DO STUFF WITH ERROR
res.end(err);
});
}
};
methods.foo = asyncMiddleware((req, res, next) => {
// assuming bizz is a utility analogous to createRandomString
// and returns a promise object which will either get resolved or rejected
// if resolved the response will end in this case and end the response
// if rejected, await will throw an error which will be caught by asyncMiddleware
var bar = await bizz(); // Asynchronous code(which will wait for a promise to be resolved)
// {... DO SYNCHRONOUS STUFF ...}
// if suppose there is an error in your synchronous code
// then it will throw an error which also will be caught by asyncMiddleware
res.end();
});

Azure Function automatic retry on failure UnhandledPromiseRejectionWarning

const fetch = require('node-fetch');
let url = 'something.com';
module.exports = function(context) {
let a = fetch(url)
a.then(res => {
if(res.status!=200) throw new Error(res.statusText)
else{
context.done(null, res.body);
}
});
a.catch(err => {
console.log(err)
throw new Error(err)
});
};
I have a durable function that calls an activity function like above. I have set automatic retry on failure on this activity function. To retry the function needs to get an error.
So In get request I want to throw an error when i get response like 404 or something similar. But when i throw from catch block i get an error like below
UnhandledPromiseRejectionWarning: Unhandled promise rejection. 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 .catch().
function pauses there and stops execution.I have to manually stop and start the execution. How can i handle this so that the function retries?
Your code branches.
Ignoring the detail, what you have is :
let a = <Promise>; // root
a.then(...); // branch_1
a.catch(...); // branch_2
So whereas you catch errors arising in a, any error arising in branch 1 will be uncaught. Hence the warning
Compare that with :
let a = <Promise>; // root
a.then(...).catch(...); // branch
or
<Promise>.then(...).catch(...); // no assignment necessary
So, you might write :
module.exports = function(context) {
return fetch(url)
.then(res => {
if(res.status!=200) {
throw new Error(res.statusText);
} else {
context.done(null, res.body);
}
})
.catch(err => {
console.log(err)
throw new Error(err)
});
};
Alternatively, depending on the required division of responsibilities between module and caller(s) ...
module.exports = function(context) {
return fetch(url)
.then(res => {
if(res.status!=200) {
throw new Error(res.statusText);
} else {
return res;
}
});
};
... and call .context.done(null, res.body); in a .then() callback in the caller.
In both cases, with return included, then the caller will need to catch errors otherwise you will again get an unhandled error warning.
Found that with the use of async/await this problem goes away and the function re-try after exception is thrown.
const fetch = require('node-fetch');
let url = 'something.com';
module.exports = async function(context) {
let res = await fetch(url)
if(res.status!=200) throw new Error(res.statusText);
else return res.body;
};

Resources