TL;DR: Em vez de usar throw usar uniões T|null. Ou para saber exatamente o erro, Either/Result.
Para sinalizar se uma função emite um erro, geralmente eu adoto o paradigma funcional.
Imagine uma função f que divide dois números reais x e y.
f(x, y) = \frac{x}{y} , \text{onde } x, y \in \mathbb{R} , \text{e} y \neq 0
Se apenas deseja saber que o erro aconteceu, geralmente a união simples basta.
O erro é sinalizado no contradomínio da função f : \mathbb{R}^{2} \to \mathbb{R} \cup \{\text{null}\},
function f(x: number, y: number): number | null {
if (y === 0) return null;
return x / y;
}
let x: number = 10;
let y: number = 0;
let z: number|null = f(x, y);
if (z == null) {
console.error("erro");
}
Entretanto se deseja saber especificamente o erro, isto tende a ser um pouco mais verboso.
type DivError = {
code: 'DIVISION_BY_ZERO';
};
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
function f(x: number, y: number): Result<number, DivError> {
if (y === 0) {
return {
ok: false,
error: {
code: 'DIVISION_BY_ZERO'
}
}
}
return {
ok: true,
value: x / y
}
}
let x: number = 10;
let y: number = 0;
let z = f(x, y);
if (z.ok) {
console.log(z.value);
} else {
console.error(z.error.code);
}
Para casos simples a primeira abordagem tende a ser o suficiente. Mas a segunda só recomendo em caso de regras de negocio complexas.