typescript - ¿Cómo se usan los errores tipados en async catch()?
Estoy usando una función async para llamar a una API existente basada en promesas que rechaza la promesa con un error tipado.
Podrías simular este comportamiento así:
interface ApiError {
code: number;
error: string;
}
function api(): Promise<any> {
return new Promise((resolve, reject) => {
reject({ code: 123, error: "Error!" });
});
}
Ahora con promesas, puedo anotar el tipo de error como ApiError:
api().catch((error: ApiError) => console.log(error.code, error.message))
Pero al usar async si intento anotar el tipo de error en try ... catch():
async function test() {
try {
return await api();
} catch (error: ApiError) {
console.log("error", error);
}
}
Se compila con error:
La variable de la cláusula catch no puede tener una anotación de tipo.
Entonces, ¿cómo sé qué tipo de error estoy esperando? ¿Necesito escribir una afirmación en el bloque catch()? ¿Es eso un error/característica incompleta de async?
Respuesta #1
En TypeScript, las variables de la cláusula catch no pueden tener una anotación de tipo (aparte de, desde TypeScript 4.0, unknown). Esto no es específico de async. Aquí hay una explicación de Anders Hejlsberg:
No permitimos anotaciones de tipo en las cláusulas catch porque realmente no hay forma de saber qué tipo tendrá una excepción. Puedes lanzar objetos de cualquier tipo y las excepciones generadas por el sistema (como la excepción de falta de memoria) técnicamente pueden ocurrir en cualquier momento.
Puedes comprobar la existencia de las propiedades error.code y error.message (opcionalmente usando un guardia de tipo definido por el usuario) en el cuerpo del catch.
Respuesta #2
No puedes asignar un tipo personalizado a tu error directamente, pero puedes usar un guardia de tipo. Un guardia de tipo es una función que ayuda al compilador de TypeScript a determinar el tipo de tu error.
Escribir una guardia de tipo con un predicado de tipo es muy fácil. Solo necesitas implementar una verificación booleana que acepte un valor y determine si este valor tiene una propiedad de tu tipo personalizado.
Dado tu ejemplo, puedo ver que tu ApiError tiene una propiedad personalizada llamada code, así que un guardia de tipo podría verse así:
function isApiError(x: unknown): x is ApiError {
if (x && typeof x === 'object' && 'code' in x) {
return true;
}
return false;
}
Observa el tipo de retorno x es ApiError. ¡Eso es un predicado de tipo!
Usando la función isApiError de arriba, será posible que TypeScript detecte tu tipo de error personalizado:
interface ApiError {
code: number;
error: string;
}
function isApiError(x: unknown): x is ApiError {
if (x && typeof x === 'object' && 'code' in x) {
return true;
}
return false;
}
try {
return await api();
} catch (error) {
if (isApiError(error)) {
// Thanks to the type guard, TypeScript knows know what "error" is
console.log(error.code);
}
}
Puedes ver un guardia de tipo en acción aquí: https://www.youtube.com/watch?v=0GLYiJUBz6k
Respuesta #3
Este error no tiene nada que ver con async. No puedes tener variables de captura con tipo.
La razón de esto es simple: los tipos en TypeScript solo existen hasta que el código se compila. Una vez que se compila, todo lo que tienes es JavaScript sin tipos.
Tener filtros de tipo en las cláusulas catch requeriría comprobar los tipos de errores en tiempo de ejecución, y simplemente no hay una manera confiable de hacer eso, así que diría que es poco probable que se llegue a soportar tal característica.
