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 await
ing 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
catch
blockreturn
early or re-throw
an exception from the catch
blockcatch
block caught an exception, and test for it in an if
condition 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 return
s from the outer function.