[ Conteúdo ] Tratamento de erros com JavaScript
Introdução:
Tratamento de erros, umas das coisas mais importantes no mundo da programação, seja em qualquer linguagem. Ou melhor, as vezes, até fora do escopo de programação se faz necessário tratamento de erro. Agora, se é tão importante assim, por que uma grande parcela das pessoas, principalmente iniciantes, negligência este tópico? As vezes, até programadores experientes não tratam erros de forma adequada, e se perguntamos, talvez eles nem saibam responder... Notei isso ao tirar dúvidas sobre isso quando estava estudando sobre, e perguntei a diversas pessoas e simplesmente me falaram "coloca um try...catch
que resolve, dentre outras coisas... Mas tudo, sempre superficial.
Resolvi fazer esse artigo para falar um pouco sobre tratamento de erros com javaScript, para que você não perca horas e horas num problema que falhou silenciosamente, mas que poderia ser capturado e resolvido em minutos... Para evitar que você fique você sente na janela com uma xicará de café e pense "será que estou na área certa?"
Ok, mas do que eu vou falar em específico? Bom, cobrirei os tipos de erros como TypeError
, SyntaxError
, AggregateError
, dentre diversos outros tipos de erros e outras técnicas como try...catch...finaly
, e um dos mais populares throw
, é claro, um pouco de outras técnicas que não só deixa o código mais limpo, como também evita bugs, otimiza a aplicação, deixa menos argumentativo e etc...
Se você chegou até aqui, significa que você tem um certo interesse em ler. Não sei suas circunstâncias, nem onde mora, qual linguagem você usa, e nem nada. Mas posso dizer que você até pode conseguir um emprego sem saber tratamento de erros, mas duvido que consiga se manter na empresa por muito tempo...
Observações
Irei listar coisas que se faz necessário saber fortemente antes de continuar esse artigo.
-
Lógica de programação: Esse aqui é o mais fundamental de todos. Irei passar uma pincelada, mas não entrarei a fundo, pois não é o foco. As chances de você não entender o que estou falando e fazendo são altas pois tudo que escrevi foi assumindo que você já sabe lógica de programação.
-
Uma base sólida em JavaScript: Se você é iniciante e caiu de paraquedas aqui, pode não entender nada... Tem muito assuntos e conteúdo para você ver antes de entrar neste tópico aqui. Depois que você aprender o que é JavaScript, como executa-lo, escopo, condicional, estrutura de dados, dentre outros conceitos fundamentais, volte aqui o mais rápido que der.
-
Noções de objetos: Irei citar objetos e construtores, que são essencialmente objetos. Então se você não domina muito bem programação orientada a objetos, pode sentir uma leve dificuldade em entender. Se você sabe:
o que é um objeto
,como acessar propriedades de um objeto
,o que são classes
ecomo a palavra chave "new" se comporta
, conseguirá entender bem o que irei fazer. -
Tomar um café (optional): vai ser um artigo um tanto longo, então recomendo um bom café para acompanhar o desenvolver do artigo.
O que é tratamento de erro?
Assim como tudo na vida devemos começar com as coisas mais simples e ir avançando na medida. A resposta para a pergunta não é muito difícil, na verdade, é bem sugestiva.
Tratar erros é nada mais que manipular eles. Mas como assim? Você tá programando de boa e "boom", ocorreu um erro, e agora? Alguns erros, conseguimos resolver, pois eles são simples. Mas existem erros que falham silenciosamente, ou erros que mostra que algo deu errado, mas nada muito claro... Ou até pior: Ocorreu um erro e você não sabe o que deu errado, pois você não tratou o erro corretamente e vai ter que debugar tudo agora...
Um bom tratamento de erro, isola o problema, ou seja, aconteceu o problema, quando batemos o olho sabemos o que foi, o tipo de erro, possivéis procedimentos, e etc... Um bom tratamento de erro evita horas perdidas de debugar tudo, pois sabemos em que parte deu erro. Um bom tratamento de erro, deixa claro para o usuário o que aconteceu, e porque aconteceu.
São muitas vantagens em tratar erros. Em resumo, Eu gosto de dizer que tratar erros é: Manipular eles como queremos, para que seja possível proceder de maneira eficaz.
Manipulando erros simples:
Vamos começar a usando throw
que eu julgo o mais simples de se aprender logo no começo. Se você manjá de inglês já sacou de throw
significa "lançar", e é exatamente isso que ele faz.
Usarei
try...catch
,object error
, mas não se preocupe. Irei explicar o que eles fazem mais a frente. Foque apenas nothrow
.
Segue o exemplo:
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw new Error('O paramêtro não é um número!');
}
}
try {
getRectArea(3, 'A');
} catch (e) {
console.error(e);
// Saída: Error: O paramêtro não é um número!
}
foque nesta linha aqui e vamos analisar ela: throw new Error('O paramêtro não é um número!');
. O throw
em prática ele lança o objeto Error
para o catch
e mostra o erro no console. "Ok, mas eu não sei ainda o que é Error e try, catch
. Não precisa esquentar a cabeça agora, irei explicar logo a seguir.
A ideia é mostrar que throw
trabalha em conjunto com os outros erros. Está tudo conectado. Sacou? Você quase sempre vai utilizar eles em conjunto para tratar seus erros.
O que ocorreu no código acima é que ao chamar a função, vai cair naquele if
onde vai verificar se ambos os paramêtros são NaN
, ou seja, não são números. Se um deles for diferente de um número, LANÇARA um ERRO com a seguinte MENSAGEM: "O paramêtro não é um número!"
Então, o que o throw
faz é pegar o erro, e lançar ele pro catch
trata-lo. Nada muito Uau, mas é de extrema importância.
lembrando que fazer isso aqui vai dar um erro:
throw
new Error('O paramêtro não é um número!');
Escreva tudo inline. Seguindo adianta, vamos para o try...catch...finaly
.
Tratando erros de maneira eficiente com try..catch...finaly
Um bom programador usará try..catch
várias vezes em seu código para trata-lo, mas de maneira correta, obviamente...
O que é um try e catch
? try
é basicamente um bloco que executa quando tudo dar certo, e quando algo da errado, o catch
entra em ação.
try {
nonExistentFunction();
} catch (error) {
console.error(error);
// Expected output: ReferenceError: nonExistentFunction is not defined
// (Note: A exata saída pode dependender do navegador)
}
Como podem ver, chamei uma função que nãoe existe, então vai cair no catch
e me retornar um erro no console.
Faz parte da sintax começar com o try
, caso contrário, returnará um erro. Segue as seguintes combinações possíveis:
try...catch
try...finally
try...catch...finally
Como pode ver, o try sempre começa primeiro, e pode ser seguido com o catch ou finally. Outra observação é que é necessário abrir um bloco de código para eles {}
. Algo assim daria errado:
try doSomething(); // SyntaxError
catch (e) console.log(e);
Ok, mas é aula de try...catch...finaly
? Não, não, se acalme. Apenas passei o pincel, para ter um embasamento melhor. O que é necessário entender é que o try
executa quando não há erros, que o catch
executa quando há erros e que o finally
, só é útil em casos específicos, você quase nunca vai usa-lo se não for para clean-up.
Agora você consegue entender melhor este código:
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw new Error('O paramêtro não é um número!');
}
}
try {
getRectArea(3, 'A');
} catch (e) {
console.error(e);
// Saída: Error: O paramêtro não é um número!
}
Mas ainda há coisas para falar sobre o try...catch...finaly
... Você pode se perguntar: "ok, devo usar ele para tudo?"
A resposta é definitivamente NÃO. A ideia é tratar erros de maneira clean-code
e não poluir ele. Trate erros em pontos estratégicos. Segue o exemplo de uma maneira errada de usar o try...catch...finaly
:
async function getLocalStorage() {
let myStorage = await JSON.parse(localStorage.getItem(key) || '[]');
if(!myStorage) {
throw new Error("Seu armazenamento está vazio!")
}
try {
return myStorage
} catch (error) {
console.log("ocorreu um erro" + error)
}
}
async function setElement() {
let myStorage = await getLocalStorage()
if(!myStorage) {
throw new Error("Seu armazenamento está vazio!")
}
try {
localStorage.setItem(key, JSON.stringfy(algumElemento));
} catch (error) {
console.log("ocorreu um erro" + error)
}
}
tá verificando de novo se o armazenamento existe? pra quê? se a outra função já faz isso? Sacou agora? Você até pode ter as ferramentas, mas se o que tá entre a tela e a cadeira não tem lógica na cabeça, não adianta de nada. Se você não conseguiu entender o problema logo de cara, ou ao menos estranhou, acredito que deveria treinar mais.
Trate errros de maneira inteligente em locais estratégicos. Para pegar os items de um localStorage
eu preciso ter algo lá, então eu verifico e trato possíveis erros, mas pra setar elementos eu preciso disso? Eu acho que não.
Sempre faça perguntas como: Eu realmente preciso fazer isso?
Error Object
Encare Error Object como uma classe pai que contém outros objetos com subclasse como SyntaxError
. Não tem muito mistério aqui, é até facil na verdade.
Podemos escrever com ou sem a palavra chave new
:
let myError = new Error()
// ou
let myError2 = Error()
Ambos os casos funciona. O ponto aqui é a manipulação dele que também não é difícil. O object error possui dois paramêtros muito importante que são name
, message
.
Vamos voltar para o nosso primeiro exemplo:
function getRectArea(width, height) {
if (isNaN(width) || isNaN(height)) {
throw new Error('O paramêtro não é um número!');
}
}
try {
getRectArea(3, 'A');
} catch (e) {
console.error(e);
// Saída: Error: O paramêtro não é um número!
}
Observe que podemos criar a instância diretamente no throw
que é o que devemos fazer. POis queremos que esse erro seja lançado para o catch
. Note que dentro dos parênteses, possui um texto. Esse ai é o nosso message
.
catch (e) {
console.error(e);
// Saída: Error: O paramêtro não é um número!
}
Observe esta parte e atente-se ao e
argument. Ele pega o argumento do error object que foi lançado pelo o throw
. Assim, podemos mostrar o erro no console, ou num modal para o usuário. Lembrando que, seja bem descritivo nos erros.
Ok, mas e a propriedade name
? Podemos utilizar ele facilmente. Não precisa, mas vou utilizar sintaxe de destruturing patterns:
catch ({name, message}) {
console.error(`Ocorreu um erro do tipo: ${name} com a seguinte mensagem: ${message}`);
// Saída: Error: Ocorreu um erro do tipo "error" com a seguinte mensagem: O paramêtro não é um número!
}
Note que o valor de name
padrão é error
e você pode alterar ele:
let myError = new Error()
let myErrorName = myError.name = "Don't it's a number"
Ok, mas no que me ajuda tudo isso que você falou? Não deu pra perceber? Ao saber o que é um throw
você pode agora lançar erros pro seu catch
com uma determinada mensagem do erro, podendo alterar o nome, a mensagem. Com o try e catch
você pode tratar seus erros da meneira que vocè quiser. Colocando nomes chaves, você consegue identificar onde o erro ocorreu, o tipo de valor, o tipo de erro e etc... Conhecendo o new Error()
você sabe a raiz de todo o tratamento de erro no javaScritp (nem todo, mas a maior parte). Só com isso, você já tem uma capacidade tecnica levada a outro nível. Porém, vai precisar de prática para dominar. Cada erro tem um tratamento específico, estude e pratique!
Para fechar com chave, citarei todos os tipos de erros que derivam do new Error()
. Isso significa que new Error()
é uma classe pai, e todos os objetos que vou citar aqui são seus filhos, portanto, tem as mesmas propriedades:
new SyntaxError()
: lança um erro quando a escrita não esta conforme o padronizado na linguagem.new ReferenceError()
: lança um erro quando a variável não existe ou não foi corretamente inicializada.new RangeError()
: lança um erro quando um valor não estã dentro do intervalo permitido. Como uma função que deveria ter 2 argumentos, ai você passa 3. Lançara umrangeError
new ObjectError()
: lança um erro genérico. Quase sempre é criado em conjunto com throw para ser lançado como uma espécie de erro costumizado.new EvalError()
: lançar um erro quando ocorre algo inesperado na função globaleval()
new AggregateError()
: embrulha vários erros em um só. Geralmente é returnado por promise.any, mas, se othrow
lançar vários erros, será embrulhando em umAggregateError()
independentemente.new TypeError()
: lança um erro quando um código executa algo que não deveria, geralmente quando tentamos colocar umastring
onde deveria ser umnumber
, por exemplo.
Todos esses erros podem ser encontrados na documentação para mais detalhes, mas no geral, saber o que eu disse acima, já é praticamente o suficiente para manipular.
Um exempl com o TypeError()
que se aplica a quase todos os erros, mas nem todos , AggregateError
é diferente:
try {
throw new TypeError("Hello");
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e.message); // "Hello"
console.log(e.name); // "TypeError"
console.log(e.stack); // Stack of the error
}
Conclusão
Foi apenas um pouco sobre tratamento de erros, ainda a muito mais para se ver. Muitas outras técnicas que vão além de try..catch
e etc... Continue se aprimorando.