O TypeScript usa o try, catch, finally e throw do JavaScript. A parte específica do TypeScript é a variável do catch: com strict ligado, ela tem o tipo unknown, então você verifica o que foi lançado antes de usá-la.
O lado de tempo de execução (como a pilha é desfeita, quais tipos de erro embutidos existem) é coberto em try/catch em JavaScript.
Por que a variável do catch é unknown
O JavaScript pode lançar qualquer coisa: um Error, uma string, um número, undefined, um objeto de uma biblioteca. O TypeScript não tem como saber qual, então com strict (a flag useUnknownInCatchVariables) a variável é unknown e você precisa estreitá-la antes de usar:
index.ts(5,34): error TS18046: 'e' is of type 'unknown'.
Sem strict, e é any e o mesmo código compila, mas dá errado em tempo de execução quando uma string é lançada: e.message é undefined, e qualquer coisa mais profunda, como e.message.length, lança um TypeError. Também não dá para resolver com uma anotação: catch (e: Error) dá o erro TS1196 (Catch clause variable type annotation must be 'any' or 'unknown' if specified).
Estreitando o erro
instanceof Error cobre tudo o que é construído sobre Error, incluindo TypeError, SyntaxError, RangeError e as suas próprias subclasses. Para qualquer outra coisa, converta o valor para string como alternativa. Um pequeno helper deixa os pontos de chamada curtos:
Um catch que trata só alguns erros deve relançar o resto: if (!(e instanceof ValidationError)) throw e;. Engolir erros desconhecidos esconde bugs reais.
Lançando erros
throw aceita qualquer expressão, e o TypeScript não a restringe. Lance objetos Error mesmo assim: eles carregam um stack trace, e toda verificação instanceof Error do código depende disso.
Uma função que sempre lança um erro tem o tipo de retorno never, que o TypeScript usa para estreitar depois da chamada:
Desde o ES2022, Error recebe um segundo argumento com um cause, que encadeia um erro de baixo nível ao que você lança: throw new Error("could not load settings", { cause: e }). Quem chama pode ler err.cause (tipado como unknown).
Classes de erro personalizadas
Uma subclasse de Error permite que quem chama diferencie falhas com instanceof e carregue dados extras. Defina name, porque o herdado é "Error" e ele aparece nos logs e em String(err):
Verifique primeiro a classe mais específica, já que um NotFoundError também é um HttpError. O modificador override é opcional aqui, a menos que noImplicitOverride esteja ligado.
Guias mais antigos adicionam Object.setPrototypeOf(this, new.target.prototype) a todo construtor de erro. Isso era necessário ao compilar classes para funções ES5, em que instanceof quebrava para subclasses de Error. O TypeScript 7 não suporta mais target: "es5" (ele reporta TS5108: Option 'target=ES5' has been removed), e com ES2015 ou posterior o class ... extends Error nativo funciona, como acima.
O padrão Result
O TypeScript não rastreia quais erros uma função pode lançar, então nada lembra quem chama de tratá-los. Para falhas esperadas (entrada inválida, um registro inexistente), retornar um valor que diz "sucesso ou falha" coloca o erro dentro do sistema de tipos:
Ler r.value antes de verificar r.ok é erro de compilação, e essa é a ideia. Use exceções para o inesperado (bugs, uma conexão caída) e valores Result para desfechos sobre os quais quem chama precisa decidir.
Erros em código assíncrono
Uma promise rejeitada vira um erro lançado no await, então try/catch em volta de await funciona do mesmo jeito, e a variável é de novo unknown. A única diferença é o .catch() de uma promise: o parâmetro do callback dele é tipado como any, não unknown, então anote-o você mesmo (.catch((e: unknown) => ...)). Veja async/await para exemplos.
Erros comuns
- Ler
e.messagesem estreitar. Comstrictnão compila; semstrict, quebra quando algo que não éErroré lançado. - Capturar tudo e seguir em frente. Trate os erros que você espera e relance o resto.
- Esquecer
namenos erros personalizados. Aí os logs dizemErrorpara todos eles. - Lançar strings.
throw "failed"não tem stack trace e falha nas verificaçõesinstanceof Error.
Perguntas frequentes
Qual é o tipo do erro em um bloco catch do TypeScript?
unknown quando strict está ligado (a flag useUnknownInCatchVariables), e any caso contrário. O JavaScript pode lançar qualquer valor, não só objetos Error, então o TypeScript obriga você a verificar. Estreite-o com if (e instanceof Error) antes de ler e.message.
Posso tipar a variável do catch como Error no TypeScript?
Não. catch (e: Error) dá o erro TS1196: Catch clause variable type annotation must be 'any' or 'unknown' if specified. Só unknown e any são permitidos, porque nada garante o que foi lançado. Faça o estreitamento dentro do bloco.
Como lançar um erro no TypeScript?
Do mesmo jeito que no JavaScript: throw new Error("message"), ou uma instância de uma subclasse embutida ou personalizada, como new RangeError(...). O TypeScript deixa você lançar qualquer valor, mas lançar objetos Error preserva o stack trace e faz as verificações instanceof Error funcionarem.
Como criar uma classe de erro personalizada no TypeScript?
Estenda Error, chame super(message, options) e defina name: class NotFoundError extends Error { name = "NotFoundError"; }. Campos extras vão no construtor. Com qualquer target suportado (ES2015 em diante), instanceof NotFoundError funciona sem correção de prototype.
O TypeScript tem checked exceptions ou uma cláusula throws?
Não. O tipo de uma função não diz nada sobre o que ela pode lançar, e o compilador nunca verifica se os erros são tratados. Para erros que quem chama deve tratar, retorne-os como valores com um tipo Result (uma discriminated union de sucesso e falha), para que o sistema de tipos faça a verificação.