How to Catch Exceptions Thrown in a JavaScript Async Callback

using try catch on error generated by asynchronous callback function

When you use setTimeout, the callback gets pushed into the event loop (moved out of the JS Engine and into the realm of the runtime/browser) and your foo function exits immedeatly.

After the timeout AND once the stack is empty, the event loop will put the callback onto the stack and run it. That's why the try/catch and the callback are independent of each other. That's also why setTimeout(1000) does not mean in a second but not earlier than and somewhat close to one second.

See What the heck is the event loop anyway? | Philip Roberts | JSConf EU

Catching Errors thrown by callback functions

The try-catch block in the code below does not work as long as someFunction is asynchronous

Of course it doesn't - someFunction is asynchronous and returns a promise. try/catch cannot catch asynchronous errors (promise rejections) unless you await that promise. If you don't call someFunction from an async function context, you will need to use the normal promise catch method:

try {
await someFunction(()=> {
// ^^^^^
console.log("Callback has been called")
throw new Error("Some error");
});

} catch (error) {
console.log(error.message)
}

or

someFunction(() => {
console.log("Callback has been called")
throw new Error("Some error");
}).catch(error => {
console.log(error.message)
});

Of course there's no reason for that someFunction to take a callback. You should rather call the someAsyncFunction directly and use the promise that it returns for installing your callback

someAsyncFunction().then(() => {
console.log("Callback has been called")
throw new Error("Some error");
}).catch(error => {
console.log(error.message)
});

or just not using a callback at all with async/await:

try {
await someAsyncFunction()
console.log("'Callback' code has been called")
throw new Error("Some error");
} catch (error) {
console.log(error.message)
}

How to catch exception in async context

The Eventemitter is fully sync and there is no workaround for it. What you can do is to use the eventemitter2 module to replace the original Eventemitter class. It is fully compatible with Eventemitter and allows you to use async actions.

var EventEmitter = require('eventemitter2');

const delaySync = function (ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};

const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');

// Just delay for 100ms and then throw
await delaySync(100);

throw new Error('Something happened');

// for async emitters pass promisify true.
}, { promisify: true });

describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});

}

Catching and returning an error from a callback that may be asynchronous

Realised that it's not necessary to chain the catch immediately! So the following abomination allows me to determine if it is a promise and handle the error accordingly:

const y = (error, callback) => function () {
try {
const returnValue = callback(...arguments);

if (returnValue instanceof Promise) {
return returnValue.catch((callbackError) => {
return callbackError;
});
}
else {
return returnValue;
}
}
catch (callbackError) {
return callbackError;
}
};

If you're wondering why this function is useful, here's an example of what I'm using it for. It modifies the error (chaining it with another error, see VError) before returning it:

const y = (error, callback) => function () {
try {
const returnValue = callback(...arguments);

if (returnValue instanceof Promise) {
return returnValue.catch((callbackError) => {
throw VError(error, callbackError);
});
}
else {
return returnValue;
}
}
catch (callbackError) {
throw VError(error, callbackError);
}
};

Note the return of the following as per the question:

const a = y(() => 42)(); // returns '42'
const b = y(async () => Promise.resolve(42))(); // returns 'Promise {42}'
const c = y(() => { throw new Error('Base'); })(); // throws VError
const d = y(async () => { throw new Error('Base'); })(); // throws VError

How to handle async callback promise rejection?

You shouldn't pass async functions into things that don't expect them (unless you catch all errors, as you are with your app.post callback). Instead, give yourself a wrapper for limiter.removeTokens that returns a promise:

function removeTokens(limiter, id) {
return new Promise((resolve, reject) => {
limiter.removeTokens(id, (err, remainingRequest) => {
if (err) {
reject(err);
} else {
resolve(remainingRequest);
}
});
});
}

(You might also look into util.promisify for that.)

Then:

app.post('/', async (req, res) => {
try {
...
await getAll(); // *** Or you might just use `removeTokens(limiter, 1)` directly here

function getAll() {
return removeTokens(limiter, 1);
}
} catch (error) {
console.log(error);
}
});

Here it is using removeTokens directly:

app.post('/', async (req, res) => {
try {
...
await removeTokens(limiter, 1);
} catch (error) {
console.log(error);
}
});

Exceptions thrown in asynchronous javascript not caught

When you are throwing the error the try {} block has been long left, as the callback is invoked asynchronously outside of the try/catch. So you cannot catch it.

Do whatever you want to do in case of an error inside the error callback function.

How to bubble up Exceptions from inside async/await functions in NodeJs?

In a nutshell, there is no way to propagate an error from the xml2js.parseString() callback up to the higher code because that parent function has already exited and returned. This is how plain callbacks work with asynchronous code.

To understand the problem here, you have to follow the code flow for xml2js.parseString() in your function. If you instrumented it like this:

app.post('/ams', async (req, res) => {
try {
console.log("1");
xml2js.parseString(xml, async (err, json) => {
console.log("2");
if (err) {
throw new XMLException();
}

// assume many more clauses here that can throw exceptions

res.status(200);
res.send("Data saved")

});
console.log("3");
} catch (err) {
if (err instanceof XML2JSException) {
res.status(400);
message = "Malformed XML error: " + err;
res.send(message);
}
}
console.log("4");
});

Then, you would see this in the logs:

1    // about to call xml2js.parseString()
3 // after the call to xml2js.parseString()
4 // function about to exit
2 // callback called last after function returned

The outer function has finished and returned BEFORE your callback has been called. This is because xml2js.parseString() is asynchronous and non-blocking. That means that calling it just initiates the operation and then it immediately returns and the rest of your function continues to execute. It works in the background and some time later, it posts an event to the Javascript event queue and when the interpreter is done with whatever else it was doing, it will pick up that event and call the callback.

The callback will get called with an almost empty call stack. So, you can't use traditional try/catch exceptions with these plain, asynchronous callbacks. Instead, you must either handle the error inside the callback or call some function from within the callback to handle the error for you.

When you try to throw inside that plain, asynchronous callback, the exception just goes back into the event handler that triggered the completion of the asynchronous operation and no further because there's nothing else on the call stack. Your try/catch you show in your code cannot catch that exception. In fact, no other code can catch that exception - only code within the exception.

This is not a great way to write code, but nodejs survived with it for many years (by not using throw in these circumstances). However, this is why promises were invented and when used with the newer language features async/await, they provide a cleaner way to do things.

And, fortunately in this circumstance xml2js.parseString() has a promise interface already.

So, you can do this:

app.post('/ams', async (req, res) => {
try {
// get the xml data from somewhere
const json = await xml2js.parseString(xml);
// do something with json here
res.send("Data saved");
} catch (err) {
console.log(err);
res.status(400).send("Malformed XML error: " + err.message);
}
});

With the xml2js.parseString() interface, if you do NOT pass it a callback, it will return a promise instead that resolves to the final value or rejects with an error. This is not something all asynchronous interfaces can do, but is fairly common these days if the interface had the older style callback originally and then they want to now support promises. Newer interfaces are generally just built with only promise-based interfaces. Anyway, per the doc, this interface will return a promise if you don't pass a callback.

You can then use await with that promise that the function returns. If the promise resolves, the await will retrieve the resolved value of the promise. If the promise rejects, because you awaiting the rejection will be caught by the try/catch. FYI, you can also use .then() and .catch() with the promise, but in many cases, async and await are simpler so that's what I've shown here.

So, in this code, if there is invalid XML, then the promise that xml2js.parseString() returns will reject and control flow will go to the catch block where you can handle the error.

If you want to capture only the xml2js.parseString() error separately from other exceptions that could occur elsewhere in your code, you can put a try/catch around just it (though this code didn't show anything else that would likely throw an exception so I didn't add another try/catch). In fact, this form of try/catch can be used pretty much like you would normally use it with synchronous code. You can throw up to a higher level of try/catch too.


A few other notes, many people who first start programming with asynchronous operations try to just put await in front of anything asynchronous and hope that it solves their problem. await only does anything useful when you await a promise so your asynchronous function must return a promise that resolves/rejects when the asynchronous operation is complete for the await to do anything useful.

It is also possible to take a plain callback asynchronous function that does not have a promise interface and wrap a promise interface around it. You pretty much never want to mix promise interface functions with plain callback asynchronous operations because error handling and propagation is a nightmare with a mixed model. So, sometimes you have to "promisify" an older interface so you can use promises with it. In most cases, you can do that with util.promisify() built into the util library in nodejs. Fortunately, since promises and async/await are the modern and easier way to do asynchronous things, most newer asynchronous interfaces in the nodejs world come with promise interfaces already.



Related Topics



Leave a reply



Submit