javascript - Sintaxis Correcta de Try...Catch Usando Async/Await
Me gusta la planitud de la nueva característica Async/Await disponible en Typescript, etc. Sin embargo, no estoy seguro de que me guste el hecho de que tenga que declarar la variable sobre la que estoy await dentro del bloque try...catch para poder usarla después. Así:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Por favor corrígeme si me equivoco, pero parece ser una buena práctica no colocar múltiples líneas de lógica de negocio en el cuerpo del try, así que sólo me queda la alternativa de declarar createdUser fuera del bloque, asignarlo dentro del bloque y luego usarlo después.
¿Cuál es la mejor práctica en este caso?
Respuesta #1
Parece ser una buena práctica no colocar múltiples líneas de lógica de negocio en el cuerpo del try
En realidad, diría que sí. Normalmente querrás capturar todas las excepciones al trabajar con el valor:
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
}
Si quieres capturar y manejar errores solo de la promesa, tienes tres opciones:
Declara la variable afuera y ramifica dependiendo de si hubo una excepción o no. Eso puede tomar varias formas, como
- asignar un valor predeterminado a la variable en el bloque
catch
- asignar un valor predeterminado a la variable en el bloque
- hacer un
returntemprano ore-throwuna excepción desde el bloquecatch - establecer una bandera sobre si el bloque
catchatrapó una excepción, y comprobarlo en una condiciónif - probar si se ha asignado un valor a la variable
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
}
Prueba la excepción capturada por su tipo, y manéjala o relánzala en función de eso.
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;
}
}
Desafortunadamente, el JavaScript estándar (todavía) no tiene soporte de sintaxis para excepciones condicionales.
Si tu método no devuelve promesas que se rechacen con errores lo suficientemente específicos, puedes hacerlo tú mismo volviendo a lanzar algo más apropiado en un manejador .catch():
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
Véase también Manejo de múltiples capturas en cadena de promesas para la versión previa a async/await de esto.
Usa then con dos callbacks en lugar de try/catch. Esta realmente es la manera menos fea y mi recomendación personal también por su simplicidad y corrección, sin depender de errores etiquetados o del aspecto del valor del resultado para distinguir entre el cumplimiento y el rechazo de la promesa:
await this.User.create(userInfo).then(createdUser => {
// user was successfully created
console.log(createdUser)
// business logic goes here
}, error => {
console.error(error) // from creation
});
Por supuesto, viene con la desventaja de introducir funciones de devolución de llamada, lo que significa que no se puede break/continue bucles tan fácilmente ni hacer return tempranos desde la función externa.
Respuesta #2
Otro enfoque más sencillo es agregar .catch a la función de promesa. ej:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Respuesta #3
Código más limpio
usando async/await con el manejador catch de Promise.
Por lo que veo, este ha sido un problema de larga data que ha molestado (ambos sentidos) a muchos programadores y su código. La Promesa .catch realmente no es diferente de try/catch.
El manejador catch de la Promesa ES6 y funciona armoniosamente con "await/async", proporcionando una solución adecuada y un código más limpio:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Tenga en cuenta que, aunque esto responde a la pregunta, consume el error. En este caso, es mejor lanzar una excepción porque (1) se espera que esta operación (crear un usuario) no falle, y (2) de todos modos no puede continuar: la operación ha generado un error:
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
¿Aprender catch no parece valer la pena?
Los beneficios de la limpieza pueden no ser evidentes a simple vista, pero se acumulan en operaciones asíncronas complejas del mundo real.
Como ilustración, además de crear un usuario (this.User.create), podemos enviar una notificación (this.pushNotification) y enviar un correo electrónico (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 y 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
})
Compone las operaciones:
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
})
Sin try/catch. Y está claro qué errores estás manejando.
