I am trying to supply an async function to node.js's fs.readFile callback, but I am not too sure how to handle the errors thrown by the callback. Normally, with non-async callback, you would do:
// error is handled
fs.readFile('/file_does_not_exist.txt', 'utf8', (err, data) => {
if (err) throw err
// do things here
})
And if a readFile error occurs, it is handled properly. But as soon as I use an async function as a callback:
// UnhandledPromiseRejectionWarning
fs.readFile('/file_does_not_exist.txt', 'utf8', async (err, data) => {
if (err) throw err
// await for things here
})
Then it throws UnhandledPromiseRejectionWarning when a readFile error occurs. How would you try to handle the err in this scenario? I have tried try catch in the body of the callback, but it doesn't work. The only other solution is to wrap the async function inside another function.
It's expected that you get an UnhandledPromiseRejectionWarning error, because throwing an error isn't the same as handling it.
If your goal is to exit the process:
if (err) {
process.exit(1);
}
If you want to continue running the program but somehow handle the error upstream, you'll end up mixing callbacks and promises, which typically leads to convoluted code.
Instead, I would suggest using fs/promises, the Promise-based implementation of fs:
const fs = require('node:fs/promises');
…
async function readFile() {
try {
const data = await fs.readFile('/file_does_not_exist.txt', 'utf8');
// process file data
} catch(e) {
console.error('caught error reading file', e);
// here you can cleanly exit the process or re-throw `e` to properly handle it upstream
}
}
fs.readFile('/file_does_not_exist.txt', 'utf8', async (err, data) => {
//if (err) throw err
/* replace it to */
if (err) {
//handle error here
} else {
// await for things here
}
})
Related
I'd like to catch all my exceptions in one place, but I can't do that currently:
There is an important thing to note if you like more try/catch. The following code won't catch the error:
[...]
Remember: a rejected Promise will propagate up in the stack unless you catch it. To catch the error properly in try/catch you would refactor like so:
whatever().catch(err => console.error(err));
But here is my code as I would like to have it:
async function getMtgJsonVersion() {
try {
const response = await axios(metaUrl).catch((err) => { throw err; });
const { data: { meta: { version } } } = response;
return version;
} catch (error) {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
}
}
and my test:
// console.error is mocked higher up in the test file
it.only('handles errors in fetching', async () => {
expect.assertions(2);
axios.mockReset(); // I use mockImplementationOnce elsewhere
axios.mockRejectedValueOnce(new Error('bang'));
expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
});
But when I run it, I get that the last expect is not fulfilled?
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: StringContaining "bang"
Number of calls: 0
I was hoping to catch all my throws in one place, but it looks like it's not as simple as I thought.
Is this possible, and how?
Because expect(fn).rejects.* is an asynchronous action, then it will take "a little time" to finish.
In your code, expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang')) will run before expect(() => getMtgJsonVersion()).rejects.toThrow('bang'); line. At that time, the console.log is not be called yet.
To make it work as your expectation, you have to wait until getMtgJsonVersion finishes, then assert on the log function. rejects.toThrow('bang') return a promise, then just wait for it with await keyword:
await expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
My note: Avoid using try/catch in "child" unit, just use it in the "final parent" function, if you just want to log when the http request is failed:
async function getMtgJsonVersion() {
const { data } = await axios(metaUrl).catch((error) => {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
});
return data.meta.version.
}
Try to handle every exception in my async code (nodeJS, ExpressJS):
Here is almost pseudo code. I use limiter (npm limiter) module with method removeTokens (num, callback(err,remainingRequest)). Big part of code is inside the callback, and I wanna catch and throw any error there to the handler, but for now the error inside callback is still marked as "unhandled exception" and I don't understand why.
app.post('/', async (req, res) => {
try {
...
return getAll();
async function getAll () {
limiter.removeTokens(1, async (err, remainingRequest) => {
try {
throw new Error('THROWN')
} catch (error) {
throw error
}
})
}
} catch (error) {
console.log(error);
}
});
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);
}
});
Firstly if possible please share as much code as you can as then it is easy for us to debug where the problem might be.
Coming you your question i think the problem is that in your try..catch block you are throwing the error instead of handling it with a reject. Below i have pasted a code block which you can try and let me know if it works for you. Please not the syntax might be different but the idea is that you have to reject the Promise in case of error.
`````````limiter.removeTokens(1, async (err, remainingRequest) => {
````````````try {
```````````````throw new Error('THROWN')
````````````} catch (error) {
```````````````return Promise.reject(error) //
````````````}
`````````})
``````}
```} catch (error) {
``````console.log(error);
```}
})
I am creating a Nodejs and express based backend application and trying to handle error in a manner which is suitable for production systems.
I use async await to handle all synchronous operations in the code.
Here is a code snippet of router end points
app.get("/demo",async (req, res, next) => {
await helper().catch(e => return next(e))
console.log("After helper is called")
res.json(1)
})
function helper(){ //helper function that throws an exception
return new Promise((resolve, reject)=> reject(new Error("Demo Error")))
}
After all routes are defined I have added a common error handler that catches exceptions. To simplify it I am adding a simple function
routes.use( (err, req, res, next) => {
console.log("missed all", err)
return res.status(500).json({error:err.name, message: err.message});
});
I expect that the code after await helper() should not execute since the exception has been handled and response sent to frontend. Instead what I get is this error.
After helper is called
(node:46) UnhandledPromiseRejectionWarning: Error
[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the
client
What is the correct way to handle error with async await?
You get After helper is called, because your code continues to execute since it did not return
Don't chain catch with async/await. You do that with Promise.
helper()
.then(data => console.log(data))
.catch(e => console.log(e))
You can handle error like:
app.get("/demo",async (req, res, next) => {
try {
await helper();
// respond sent if all went well
res.json(something)
catch(e) {
// don't need to respond as you're doing that with catch all error handler
next(e)
}
})
you can use try catch to handle the situation
app.get("/demo",async (req, res, next) => {
try {
await helper()
console.log("After helper is called")
res.json(1)
} catch(err) {
next(err)
}
})
function helper(){ //helper function that throws an exception
return new Promise((resolve, reject)=> reject(new Error("Demo Error")))
}
I want to throw an error exception for an redis.set callback and catch in try-catch block and then get control to error handling express middleware.
try {
redis.get('key', (err, reply) => {
if(err) throw err;
if(!reply) throw new Error('Can't find key');
});
}
catch{
next(error);
}
the problem is, that try-catch is simply not working, error is going to node console, but server is responding with 200 status.
You cant catch async events. Use promises for that:
const getKey = new Promise((res,rej) => {
redis.get('key', (err, reply) => {
if(err) return rej(err);
res(reply);
});
});
So one can do:
getKey.catch(next);
getKey.then(reply => {
//do whatever
next();
});
Node 8.1.2, I have a structure where one file is calling another file's function in a map. In a real example I would use Promise.all on the map but that's not the question here. Here is the structure:
A.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
return b(thing)
}))
return res.status(200).json(...)
}
B.js:
// Thing -> Promise<Object>
function b (thing) {
return ThingModel.update(...) // this returns a Promise but FAILS and throws an errror
}
module.exports = { b }
OK. So in function b I try to get some async data (from a database). It fails and throws an Uncaught Promise Rejection.
How to make deal with it?
I tried multiple solutions:
A1.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
try {
return b(thing)
} catch (err) {
return next(err)
}
}))
return res.status(200).json(...)
}
But that is still uncaught.
A2.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
try {
things.map(thing => {
return b(thing)
}))
} catch (err) {
return next(err)
}
return res.status(200).json(...)
}
Still unhandled. I tried using Promise.all, I tried double try-catch blocks (since I thought the one inside map might be returning next from the to the map result and not actually from expressStuff function. Still nothing.
The closes I got to the answer was handling the error but then code wouldn't wait for it to be thrown and both res.status() and next would work resulting in race conditions and cannot set headers after they are sent errors.
All I want to do is for the function b to throw an error but catch it in the expressStuff so I can rethrow custom UnprocessableEntityError and pass it to next. It seems like error from file B is not bubbling up to the map where it is called.
How do I do it?
EDIT:
The only way I can make this rejection handled is try-catching it in the B.js. But if I try to rethrow an error/return it - nothing. Error is swallowed. If I try to console.log it - it will be logged though.
DETAILS:
Thanks to marked answer I refactored my actual code and made it to work perfectly.
function expressStuff (res, req, next) {
try {
await Promise.all(things.map(async thing => {
if (ifSomething()) {
await b(thing)
}
}))
} catch (err) {
return next(new MyCustomError('My Custom Error Message'))
}
return res.status(200).json(...)
}
Handling rejections with try/catch works only in async functions when you await the promise - which you haven't attempted yet.
You could do either
async function expressStuff (req, res, next) {
var results;
try {
results = await Promise.all(things.map(b)); // throws when any of the promises reject
} catch (err) {
return next(err) // handle error
}
return res.status(200).json(...)
}
or (like Wait until all ES6 promises complete, even rejected promises)
function expressStuff (req, res, next) {
const resultPromises = things.map(async (thing) => {
try {
return await b(thing); // throws when the promise for this particular thing rejects
} catch (err) {
return defaultValue; // handle error - don't call `next` here
}
});
…
return res.status(200).json(...)
}