I have this code:
on('connection', async(socket) => {
try {
const promises = members.map(async(member) => {
try {
const users = await User.find({})
const _promises = users.map(async() => {
try {
//some code here
} catch (err) {
console.log(err)
}
})
await Promise.all(_promises)
} catch (err) {
console.log(err)
}
})
await Promise.all(promises)
} catch (err) {
console.log(err)
throw err
}
})
As you can see I have a try catch for each nested async function. Would it be possible to tweak the code to use only a single catch, or to simplify things somehow?
You can have just a single await at the top level of the handler. If the Promises spawned inside it all chain together to a Promise which is awaited at the top level (like in your example), that top await will catch errors thrown anywhere inside.
But, your catch section should not throw another error unless .on handles Promise rejections as well, otherwise you'll get an unhandled Promise rejection:
on('connection', async(socket) => {
try {
const promises = members.map(async(member) => {
const users = await User.find({})
const _promises = users.map(async() => {
//some code here which may throw
});
await Promise.all(_promises);
});
await Promise.all(promises);
} catch (err) {
console.log(err);
}
})
If await User.find throws, then the promises array will contain a Promise which rejects, which mean that the top await Promise.all(promises); will throw and be caught
If something inside users.map throws, _promises will contain a Promise which rejects, so await Promise.all(_promises); will throw. But that's inside the const promises = members.map callback, so that will result in promises containing a rejected Promise too - so it will be caught by the await Promise.all(promises); as well.
If a function you call throws an error, the error will fall back to the nearest catch block the function call is enclosed in.
try {
throw "an error";
}
catch(e) {
console.log(e); //output: "an error"
}
Now consider this
try {
try {
throw "an error";
}
catch(e) {
console.log(e); //output: "an error"
}
}
catch(e) {
console.log(e); //This line is not going to be executed
}
This mechanism enables attachment of more error information to the generated error in each level. Imagine your error is an object and each nested catch bock attaching its own information to the error object an pass it along by throwing again.
Look at the following code:
try {
try {
throw {internalError: 101};
}
catch(e) {
//Attach more info and throw again
e.additionalInfo = 'Disconnected when gathering information';
throw e;
}
}
catch(e) {
e.message = 'Cannot get information'
console.log(e); //Final error with lot of information
}
Related
I have the following function:
exports.signup = async(req, res) => {
console.log('signup');
const user = new User({
username: req.body.username,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8)
});
try {
if (await user.save()) {
if (isNonEmptyArray(req.body.roles)) {
// How do I catch this error? can be a role error or db error
const roles = await Role.find({name: { $in: req.body.roles }}).exec()
user.roles = roles.map(role => role._id);
if (await user.save()) {
success(res, 'Registered!');
}
} else {
// How do I catch this error? can be a role error or a db error
const role = await Role.findOne({name: 'user'}).exec();
user.roles = [role._id];
if (await user.save()) {
success(res, 'Registered!');
}
}
}
} catch(error) {
fail(res, {message: 'Database internal error occured.'});
}
};
Is it correct that the catch will trigger for all errors in the block including calls to await Role.find({name: { $in: req.body.roles }}).exec()? How would I catch this error independently? Do I need to add a try and catch within the try and catch statement?
like you said you can use another try-catch block to distinguish which error are you catching.
try {
const role = await Role.findOne({name: 'user'}).exec();
} catch(err) {
console.log(err);
}
Another idea might be to use the promises and catch the error in each .catch segment, for example
var query = Role.findOne({name: 'user'});
query.exec().then(function () {
// handle success
}).catch(function (err) {
// handle error
});
anyway there are some important to feature to keep in mind when using async/await and try-catch block, I'll put here the conclusion of an article and the link to it if you are interested:
conclusion:
We can use try...catch for synchronous code.
We can use try...catch (in combination with async functions) and the .catch() approaches to handle errors for asynchronous code.
When returning a promise within a try block, make sure to await it if you want the try...catch block to catch the error.
Be aware when wrapping errors and rethrowing, that you lose the stack trace with the origin of the error.
font: https://itnext.io/error-handling-with-async-await-in-js-26c3f20bc06a
For example
randomLibPromise.then((data, err) => { // do something with err })
how would I translate that with await?
This promise comes from a library so I don't have control over the fact that the error goes through then instead of catch. What I have is:
let data = await randomLibPromise.catch(err)
but I don't get that second parameter and I can't retrieve the error.
Every example I find on Google talk about catch for error handling but not then.
To "fix" the weird promise, you could just throw err if there is one, and return the data otherwise:
var fixed = randomLibPromise.then((data, err) => {
if(err) throw err;
return data;
})
The result will be a promise which does the correct thing, i.e. passes the data to a then, an error to a catch or, if awaiting, either returns data or throws the error.
fixed.then(data => { /* handle data */ }).catch(err => { /* handle err */ });
// or
try {
var data = await fixed;
// handle data
} catch(err) {
//handle err
}
Will the following JavaScript try catch block still catch errors if I don't have await in front of my asynchronous function as shown below?
async () => {
try {
someAsynchronouseFunction();
} catch (err) {
// will errors from my asynchronous function still be caught here?
}
}
No, they won't, because someAsynchronouseFunction(); will only resolve to a rejected Promise, and a rejected Promise alone doesn't result in an error, even in a try block. Instead, you would just see Uncaught (in promise) Error: err! in console:
async function someAsynchronouseFunction() {
await new Promise(res => setTimeout(res, 1000));
console.log('end of someAsynchronouseFunction, about to throw');
throw new Error('err!');
}
const foo = async () => {
try {
someAsynchronouseFunction();
} catch (err) {
console.log('error found: ' + err);
}
console.log('end of foo');
}
foo();
I like the flatness of the new Async/Await feature available in Typescript, etc. However, I'm not sure I like the fact that I have to declare the variable I'm awaiting on the outside of a try...catch block in order to use it later. Like so:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Please correct me if I'm wrong, but it seems to be best practice not to place multiple lines of business logic in the try body, so I'm left only with the alternative of declaring createdUser outside the block, assigning it in the block, and then using it after.
What is best practice in this instance?
It seems to be best practice not to place multiple lines of business logic in the try body
Actually I'd say it is. You usually want to catch all exceptions from working with the value:
try {
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
// business logic goes here
} catch (error) {
console.error(error) // from creation or business logic
}
If you want to catch and handle errors only from the promise, you have three choices:
Declare the variable outside, and branch depending on whether there was an exception or not. That can take various forms, like
assign a default value to the variable in the catch block
return early or re-throw an exception from the catch block
set a flag whether the catch block caught an exception, and test for it in an if condition
test for the value of the variable to have been assigned
let createdUser; // or use `var` inside the block
try {
createdUser = await this.User.create(userInfo);
} catch (error) {
console.error(error) // from creation
}
if (createdUser) { // user was successfully created
console.log(createdUser)
// business logic goes here
}
Test the caught exception for its type, and handle or rethrow it based on that.
try {
const createdUser = await this.User.create(userInfo);
// user was successfully created
console.log(createdUser)
// business logic goes here
} catch (error) {
if (error instanceof CreationError) {
console.error(error) // from creation
} else {
throw error;
}
}
Unfortunately, standard JavaScript (still) doesn't have syntax support for conditional exceptions.
If your method doesn't return promises that are rejected with specific enough errors, you can do that yourself by re-throwing something more appropriate in a .catch() handler:
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
See also Handling multiple catches in promise chain for the pre-async/await version of this.
Use then with two callbacks instead of try/catch. This really is the least ugly way and my personal recommendation also for its simplicity and correctness, not relying on tagged errors or looks of the result value to distinguish between fulfillment and rejection of the promise:
await this.User.create(userInfo).then(createdUser => {
// user was successfully created
console.log(createdUser)
// business logic goes here
}, error => {
console.error(error) // from creation
});
Of course it comes with the drawback of introducing callback functions, meaning you cannot as easily break/continue loops or do early returns from the outer function.
Another simpler approach is to append .catch to the promise function. ex:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Cleaner code
using async/await with Promise catch handler.
From what I see, this has been a long-standing problem that has bugged (both meanings) many programmers and their code. The Promise .catch is really no different from try/catch.
Working harmoniously with await/async, ES6 Promise's catch handler provides a proper solution and make code cleaner:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Note that while this answers the question, it gobbles up the error. The intention must be for the execution to continue and not throw. In this case, it's usually always better to be explicit and return false from catch and check for user:
.catch(error => {
console.error(error);
return false
})
if (!createdUser) // stop operation
In this case, it is better to throw because (1) this operation (creating a user) is not expected to failed, and (2) you are likely not able to continue:
const createUser = await this.User
.create(userInfo)
.catch(error => {
// do what you need with the error
console.error(error)
// maybe send to Datadog or Sentry
// don't gobble up the error
throw error
})
console.log(createdUser)
// business
// logic
// goes
// here
Learning catch doesn't seem like worth it?
The cleanliness benefits may not be apparent above, but it adds up in real-world complex async operations.
As an illustration, besides creating user (this.User.create), we can push notification (this.pushNotification) and send email (this.sendEmail).
this.User.create
this.User.create = async(userInfo) => {
// collect some fb data and do some background check in parallel
const facebookDetails = await retrieveFacebookAsync(userInfo.email)
.catch(error => {
// we can do some special error handling
// and throw back the error
})
const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)
if (backgroundCheck.pass !== true) throw Error('Background check failed')
// now we can insert everything
const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })
return createdUser
}
this.pushNotifcation and this.sendEmail
this.pushNotification = async(userInfo) => {
const pushed = await PushNotificationProvider.send(userInfo)
return pushed
})
this.sendEmail = async(userInfo) => {
const sent = await mail({ to: userInfo.email, message: 'Welcome' })
return sent
})
Compose the operations:
const createdUser = await this.User
.create(userInfo)
.catch(error => {
// handle error
})
// business logic here
return await Promise.all([
this.pushNotification(userInfo),
this.sendEmail(userInfo)
]).catch(error => {
// handle errors caused
// by pushNotification or sendEmail
})
No try/catch. And it's clear what errors you are handling.
I usually use the Promise's catch() function to return an object with an error property on failure.
For example, in your case i'd do:
const createdUser = await this.User.create(userInfo)
.catch(error => { error }); // <--- the added catch
if (Object(createdUser).error) {
console.error(error)
}
If you don't like to keep adding the catch() calls, you can add a helper function to the Function's prototype:
Function.prototype.withCatcher = function withCatcher() {
const result = this.apply(this, arguments);
if (!Object(result).catch) {
throw `${this.name}() must return a Promise when using withCatcher()`;
}
return result.catch(error => ({ error }));
};
And now you'll be able to do:
const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
console.error(createdUser.error);
}
EDIT 03/2020
You can also add a default "catch to an error object" function to the Promise object like so:
Promise.prototype.catchToObj = function catchToObj() {
return this.catch(error => ({ error }));
};
And then use it as follows:
const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
console.error(createdUser.error);
}
#Bergi Answer is good, but I think it's not the best way because you have to go back to the old then() method, so i think a better way is to catch the error in the async function
async function someAsyncFunction(){
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
}
someAsyncFunction().catch(console.log);
But what if we have many await in the same function and need to catch every error?
You may declare the to() function
function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
And then
async function someAsyncFunction(){
let err, createdUser, anotherUser;
[err, createdUser] = await to(this.User.create(userInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`createdUser is ${createdUser}`);
[err, anotherUser] = await to(this.User.create(anotherUserInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`anotherUser is ${anotherUser}`);
}
someAsyncFunction();
When reading this its: "Wait to this.User.create".
Finally you can create the module "to.js" or simply use the await-to-js module.
You can get more information about to function in this post
await this.User.create(userInfo).then(async data => await this.emailService.sendEmail(data.email), async error => await this.sentryService.sendReport(error))
I'm trying to handle a custom error that my async method throws, but the try catch block doesn't work appropriately.
I think the way I'm doing it should work but the error is not caught and the program terminates by displaying it in the terminal.
Here is where it throws the error:
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
await model.findOneAndUpdate({ id: tap.id }, tap, (err, result) => {
let error = null;
if (!result) {
throw new Error('Tap doesn\'t exists', 404);
}
return result;
});
}
Then, the error handling code:
async setupTapHandler(request, h) {
const tapData = {
id: request.params.id,
clientId: request.payload.clientId,
beerId: request.payload.beerId,
kegId: request.payload.kegId,
};
try {
await this.kegeratorApi.setupTap(tapData);
} catch (e) {
if (e.code === 404) return h.response().code(404);
}
return h.response().code(204);
}
Can someone help me?
I also looked at other topics:
Correct Try...Catch Syntax Using Async/Await
How to properly implement error handling in async/await case
You can only use await to successfully wait on an async operation if you are awaiting a promise. Assuming you are using mongoose, I don't know mongoose really well, but it appears that model.findOneAndUpdate() does not return a promise if you pass it a callback. Instead, it executes and puts the result in the callback.
In addition, doing a throw from a callback like this just throws into the database (the code that called the callback) and won't do you any good at all. To have a throw make a rejected promise, you need to either be throwing from the top level of an async function or be throwing from inside a .then() or .catch() handler or inside a promise executor function. That's where throw makes a promise rejected.
The key here is that you want to use the promise interface to your database, not the callback interface. If you don't pass a callback, then it returns a query which you can use .exec() on to get a promise which you can then use with await.
In addition, you weren't building an error object that would have a .code property set to 404. That isn't a property that is supported by the Error object constructor, so if you want that property, you have to set it manually.
I'd suggest this:
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
let result = await model.findOneAndUpdate({ id: tap.id }, tap).exec();
if (!result) {
let err = new Error('Tap doesn\'t exists');
err.code = 404;
throw err;
}
return result;
}
Or, with only one async operation here, there's really not much benefit to using await. You could just do this:
setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
return model.findOneAndUpdate({ id: tap.id }, tap).exec().then(result => {
if (!result) {
let err = new Error('Tap doesn\'t exists');
err.code = 404;
throw err;
}
return result;
});
}
The function findOneAndUpdate returns a promise so there should be no need for the callback. If a callback is needed and you can't update to a newer version then maybe wrap calls in a promise (under To use a callback api as promise you can do:)
Then you want to set code on error, you can't do that with the constructor.
async setupTap(tap) {
const model = this.connection.model('Tap', TapSchema);
const result = await model.findOneAndUpdate({ id: tap.id }, tap);
if (!result) {
const e = new Error('Tap doesn\'t exists');
e.code = 404;
throw(e);
}
return result;
}
async setupTapHandler(request, h) {
const tapData = {
id: request.params.id,
clientId: request.payload.clientId,
beerId: request.payload.beerId,
kegId: request.payload.kegId,
};
try {
await this.kegeratorApi.setupTap(tapData);
} catch (e) {
if (e.code === 404) return h.response().code(404);
}
return h.response().code(204);
}