¿Por qué Promise.reject().catch(() => 5) es válido para TypeScript?
¿Podrías ayudarme a entender por qué el compilador de TS no se queja de esta declaración?
Promise.reject().catch(() => 5)
Si revisamos la definición de la función manejadora dentro del catch, podemos ver el fragmento de código como sigue:
interface Promise<T> {
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;
}
Se asume que la función manejadora devuelve TResult | PromiseLike<TResult> donde TResult=nunca. Y luego, en mi ejemplo, una función manejadora devuelve 5 que es un número. Entonces podría asumir que 5 es nunca | PromiseLike<nunca>. Pero no es el caso. Un número no tiene función .then, así que no es un PromiseLike. ¡Gracias!
Respuesta #1
TypeScript no se queja porque el parámetro de tipo TResult en esa definición del método catch() simplemente tiene como valor por defecto never. El parámetro de tipo TResult no está restringido a never de ninguna manera.
Tenga en cuenta que el operador = dentro de una declaración de parámetro de tipo es un argumento de tipo por defecto, mientras que el operador extends dentro de una declaración de parámetro de tipo es una restricción de parámetro de tipo. En general, puede no tener ninguno, tener cualquiera o tener ambos. Una declaración de parámetro de tipo en una firma de llamada como <T extends C=D>(⋯) => ⋯ significa que T debe ser asignable a C, y si T no se puede inferir, se establecerá por defecto a D (que también debe ser asignable a la restricción C). Si omite C, entonces es equivalente a especificarlo como el tipo unknown, y si omite D, entonces es equivalente a especificarlo como C. Por lo tanto, <TResult=never> es equivalente a <TResult extends unknown=never>, lo que significa que se establece por defecto a never pero no está restringido en absoluto.
Entonces: TResult solo se reducirá a never en caso de que no pueda determinarse a partir de la entrada, como si no se proporciona el argumento onrejected. Pero sí proporcionaste un argumento onrejected de () => 5. Y así TypeScript pudo inferir el argumento de tipo TResult como number, porque () => 5 es un ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null válido. Puedes verificar esto pasando el cursor sobre la llamada catch en un IDE con IntelliSense:
Promise.reject().catch(() => 5)
// (method) Promise<never>.catch<number>(
// onrejected?: ((reason: any) => number | PromiseLike<number>) | null | undefined
// ): Promise<number>
Entonces todo está funcionando como se esperaba y como se pretende aquí.
Respuesta #2
Dado que la función de devolución de llamada devuelve un valor de 5, la nueva promesa creada por catch() se resuelve con ese valor. Es decir, la nueva promesa creada por catch() se resuelve con el valor de 5 porque la función de devolución de llamada devuelve ese valor.
