I'm using mongoose + express to build a simple MERN app.
I need to create multiple documents and save them, but I need to catch all errors.
I'm using this code and it works, but I'd like to handle all errors at once, not repeat the same code multiple times.
If I use try...catch block and remove the callback error handler, I obtain UnhandledPromiseRejectionWarning.
model.save((err, doc) => {
if (err) return console.error(`ERR ${err.message}`);
});
I've tried this:
export const init = async () => {
try {
const newDoc = new MyModel({ test: 'test'});
const savedDoc = await newDoc.save();
console.log('All done :)');
} catch (err) {
console.log('Error');
res.status(400).send(err);
}
}
But I can't catch the error: in debug mode, the program never enter the catch block and I obtain, in case of error for example:
UnhandledPromiseRejectionWarning: MongoError: E11000 duplicate key error collection
Any suggestion?
model.save()
.then(success => {
if(!success) {
// Handle your error
}
// Success
return model2.save();
})
.then(success2 => {
})
// etc..
.catch(err => {
// Handle your error
});
try{
const savedModel = await model.save();
console.log("Model created successfully");
res.status(200).send("Model created successfully");
}catch (err){
console.log(err);
res.status(400).send(err);
}
Related
I know for a fact that reading data from firebase with firebase admin returns multiple callbacks. that is why I use ref.once(), like example below:
const ref = db.ref('clients');
ref.once('value', (snapshot) => {
res.send(snapshot.val());
}, (errorObject) => {
console.log('The read failed: ' + errorObject.name);
});
But, when I try to update data I get into the same trouble of receiving multiple callbacks crashing my application, but I can't use once in ref.update, what can I do to prevent receiving multiple callbacks?
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client}).then(function(){
console.log("Data saved successfully.");
res.status(200).send("successful")
}).catch(function(error) {
console.log("Data could not be saved." + error);
res.status(201).send("failed")
});
});
Here is a code example.
When interacting with responses in the way you've shown, you might find that using the .then(onFulfilled, onRejected) variant of the then() method may be of use.
Quick note: When using this approach, care must be taken to understand that if the onFulfilled handler throws an exception/rejects, the sibling onRejected handler will not be called with its exception. The exception is instead passed onto the next chained onRejected handler in later then/catch calls. The sibling will only catch exceptions/rejections from steps prior to it in the chain.
Here is an example of the difference:
const somePromise = /* ... */;
const resultPromise = somePromise
.then((data) => { /* handle data */ }) // an error thrown here
.catch((err) => { /* handle error */ }) // gets caught here
// vs.
const resultPromise = somePromise
.then(
(data) => { /* handle data */ }, // an error thrown here, will reject resultPromise
(err) => { /* handle error */ } // instead of being handled here
)
This trait of the onRejected handler in .then(onFulfilled, onRejected) can be applied in a way where headers can't be sent twice for the same response. If for whatever reason the onFulfilled handler throws an exception while trying to send a response, the onRejected handler that is also responsible for sending a response is skipped - preventing any headers already sent errors.
This means that the first code block gets swapped out for:
const ref = db.ref('clients');
ref.once('value')
.then(
(snapshot) => { // got data successfully
console.log('got data successfully');
// don't forget to check snapshot.exists() if data could be missing
res.send(snapshot.val()); // using .json() over .send() is recommended for arbitrary data
},
(error) => { // failed to get data/permission
console.error('Failed to read data at /clients: ', error);
res.status(500).send('Data unavailable.');
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
and the second code block for:
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client }) // uid_client is undefined?
.then(
() => {
console.log("Data updated successfully.");
res.status(200).send("successful");
},
(error) => {
console.error("Data could not be saved.", error);
res.status(500).send("failed"); // don't use HTTP 201 Created here
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
});
The error handler that logs response errors could be rewritten so that it can be reused by taking in the relevant response object (so it can terminated when needed) and returning the error handler:
const buildResponseErrorHandler = (response) => ((error) => {
console.error('Failed to send response to client: ', error);
try { response.end() } catch (e) {} // forcefully terminate connection if not already
});
// usage:
somePromise
.then(
sendResponseHandlerForSuccess,
sendResponseHandlerForFailure
)
.catch(buildResponseErrorHandler(res)) // buildResponseErrorHandler(res) returns (err) => { /* logs problem */ }
I'm making a couple async API callouts and may throw a custom error depending on the outcome.
I'm deleting Objects from S3.
try {
await s3.deleteObject(bucketParams);
//S3 API doesn't provide resp on if obj successfully deleted. Therefore, check that it doesn't exist afterwards to verify instead
const err = await s3.headObject(bucketParams);
if (err & err.code === 'NotFound') return { code:200, message:`${key} successfully deleted` }
throw `Error deleting ${key}`;
} catch (error) {
throw new Error(500, error);
}
So the main catch can handle any exceptions thrown by those couple API callouts (error with network, bad key, bad authorization, etc..)
However, if the object didn't actually get deleted (so if it still exists)..I throw a custom error to get reported back to end user.
My question is if there's a better pattern to just throwing a custom error in your try and then having it get slurped up by your catch.
Thanks!
You can do a lot to be honest, you just need to work out what you want to capture. Heres a basic example. If you were using TypeScript you would get more power and type safety
class S3ObjectNotFound extends Error {
// Add other attributes you might like
code
constructor(message) {
super(message)
this.name = 's3/object-id-not-found'
this.code = 404
}
}
const someFunction = async (bucketParmas, key) => {
try {
try {
await s3.deleteObject(bucketParams)
} catch (s3Error) {
throw new S3ObjectNotFound(`Object with id ${bucketParmas} was not found`)
}
const err = await s3.headObject(bucketParams)
if (err && err.code === 'NotFound') {
return { code: 200, message: `${key} successfully deleted` }
}
throw AnotherErrorYouCouldMake()
} catch (error) {
throw new Error(500, error)
}
}
someFunction({...YourParams}, 'YourKey')
I have this error handler that retreives specific error messages based on what happens. But the thing is when I run my error handler function with .catch() it will work if i'm logging to the node console, but when i try send it to the client via res.json() it will only send the status code, not any part of my error handler.
function errorHandler(error){
if (error.name === 'SequelizeValidationError') {
const errors = error.errors.map(err => err.message);
return errors;
} else {
throw error;
}
}
router.post('/create', async(req, res) => {
await Movie.create(req.body)
.then(() => res.json("Movie Created"))
.catch( err => res.status(401).json(errorHandler(err)) );
});
This is my code for the error handler and the route i'm talking about. It works in the node console, but like I said it only sends the status 401 code to the client and nothing else. How can I get my error message send to the client as well?
Thank you!
Because its not waiting for result from errorHandler. Make them wait for it.
Try this.
function errorHandler(error, cb){
if (error.name === 'SequelizeValidationError') {
const errors = error.errors.map(err => err.message);
cb(errors);
} else {
throw error;
}
}
router.post('/create', async(req, res) => {
await Movie.create(req.body)
.then(() => res.json("Movie Created"))
.catch( err => {
errorHandler(err, function(errors){
res.status(401).json(errors);
});
});
})
Or you can return a Promise and await on errorHandler.
I have an express route for handling password resets, and with that i have a route, where i first find the user, and have some error handling with that, but now i want aditional error handling inside a nested function, and I'm not sure what pattern to use
function triggerPasswordResetEmailSend(req, res, next) {
var email = req.body.email;
if (!email) return res.status(422).json({error: "Please provide an email."});
UserRepositoryClass.findUserByEmail(email).then(user =>{
if(!user) return res.status(422).json({message: "User not found"})
sendPasswordReset(user);
return res.status(200).json({user: user});
}).catch(err =>{
return res.status(500).json({error: err})
});
}
Inside this function i do some initial error handling. The issue now is that the sendPasswordReset function can also throw errors, but there are not caught by the .catch() function, so I'm looking for something to handle this function.
I have tried passing the req and res objects into the function, but that does not seem like a good solution. I could do some try catch or maybe return a promise. But i want to ensure, that i follow the same pattern and best practises as i have already tried to do.
Here is the code snippet from my mail function:
module.exports = (user) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
return sendMail(options(user.email, passwordResetUrl));
}
You can use promise instead of function.
module.exports.sendPasswordReset = user = new Promise((resolve, reject) => {
const userResetToken = generatePasswordToken();
UserRepositoryClass.setPasswordResetToken(user.id, userResetToken);
const passwordResetUrl = PASSWORD_RESET_URL(user._id, userResetToken);
sendMail(options(user.email, passwordResetUrl))
.then(response => {
resolve(response, null); // we can get result as (data,error) here error is null
})
.catch(err => {
reject(null, err); // here response is null
});
});
You can use sendPasswordReset Promise like this:
sendPasswordReset(user).then((res, err) => {
// here you can get your res as well as err
if (err) throw new Error("Error while sending an email");
console.log("response", res);
});
I have a server side written in node.js and nestJs, querying with typeorm.
I'm trying to wrap some query's to the database with transaction as suggested here with some changes inspired by typeorm's docs, like this:
getManager().transaction(async transactionalEntityManager => {
transactionalEntityManager.save<Entity>(newEntity)
transactionalEntityManager.save<Entity1>(newEntity1)
});
The transaction works well and rollback the database if there was an error.
tested this way:
getManager().transaction(async transactionalEntityManager => {
transactionalEntityManager.save<Entity>(newEntity)
throw 'There is an error'
transactionalEntityManager.save<Entity1>(newEntity1)
});
The execution of the transaction is inside a graphQL Mutation, so I should return an error to the client if something went wrong, the problem is that I can't catch the errors from the transaction.
Tried doing this:
#Mutation(returns => Entity)
async create(): Promise<Entity> {
let entity = null;
let error = null;
getManager().transaction(async transactionalEntityManager => {
try {
entity = await transactionalEntityManager.save<Entity>(newEntity)
await transactionalEntityManager.save<Entity1>(newEntity1);
} catch (err) {
error = err
}
})
if (error) {
return error
}
return entity
}
when I throw error I catch it successfully, but when a real error occurs I can console.log() it in the server but it never reaches to return to the client.
You are not awaiting your transaction to finish thus, the exception can be thrown after your function call end and you don't get the exception.
Just await your transaction and it should be fine.
await getManager().transaction(async transactionalEntityManager => {
...
throw 'ERROR THAT SHOULD BE CATCHED'
}
Also it returns the result of the inner function so it can be useful.
const ok = await getManager().transaction(async transactionalEntityManager => {
await transactionalEntityManager.save<Entity>(newEntity)
await transactionalEntityManager.save<Entity>(newEntity2)
return 'OK'
}