Um guia definitivo para lidar com erros em JavaScript
Publicados: 2022-01-24A lei de Murphy afirma que tudo o que pode dar errado acabará dando errado. Isso se aplica um pouco bem demais no mundo da programação. Se você criar um aplicativo, é provável que crie bugs e outros problemas. Erros em JavaScript são um problema tão comum!
O sucesso de um produto de software depende de quão bem seus criadores podem resolver esses problemas antes de prejudicar seus usuários. E JavaScript, de todas as linguagens de programação, é notório por seu design médio de tratamento de erros.
Se você estiver construindo um aplicativo JavaScript, há uma grande chance de você errar com os tipos de dados em um ponto ou outro. Se não for isso, você pode acabar substituindo um indefinido por um operador nulo ou triplo igual ( ===
) por um operador duplo igual ( ==
).
É apenas humano cometer erros. É por isso que mostraremos tudo o que você precisa saber sobre como lidar com erros em JavaScript.
Este artigo o guiará pelos erros básicos em JavaScript e explicará os vários erros que você pode encontrar. Você aprenderá a identificar e corrigir esses erros. Há também algumas dicas para lidar com erros de forma eficaz em ambientes de produção.
Sem mais delongas, vamos começar!
O que são erros de JavaScript?
Erros na programação referem-se a situações que não permitem que um programa funcione normalmente. Isso pode acontecer quando um programa não sabe como lidar com o trabalho em questão, como ao tentar abrir um arquivo inexistente ou acessar um endpoint de API baseado na Web enquanto não há conectividade de rede.
Essas situações forçam o programa a lançar erros ao usuário, informando que ele não sabe como proceder. O programa coleta o máximo de informações possível sobre o erro e, em seguida, informa que não pode avançar.
Programadores inteligentes tentam prever e cobrir esses cenários para que o usuário não precise descobrir uma mensagem de erro técnico como “404” de forma independente. Em vez disso, eles mostram uma mensagem muito mais compreensível: “A página não pôde ser encontrada”.
Erros em JavaScript são objetos mostrados sempre que ocorre um erro de programação. Esses objetos contêm amplas informações sobre o tipo do erro, a instrução que causou o erro e o rastreamento de pilha quando o erro ocorreu. O JavaScript também permite que os programadores criem erros personalizados para fornecer informações extras ao depurar problemas.
Propriedades de um erro
Agora que a definição de um erro de JavaScript está clara, é hora de mergulhar nos detalhes.
Erros em JavaScript carregam certas propriedades padrão e personalizadas que ajudam a entender a causa e os efeitos do erro. Por padrão, os erros em JavaScript contêm três propriedades:
- message : Um valor de string que carrega a mensagem de erro
- name : O tipo de erro que ocorreu (Vamos nos aprofundar nisso na próxima seção)
- stack : O rastreamento de pilha do código executado quando ocorreu o erro.
Além disso, os erros também podem carregar propriedades como columnNumber, lineNumber, fileName, etc., para descrever melhor o erro. No entanto, essas propriedades não são padrão e podem ou não estar presentes em todos os objetos de erro gerados a partir de seu aplicativo JavaScript.
Entendendo o rastreamento de pilha
Um rastreamento de pilha é a lista de chamadas de método em que um programa estava quando ocorre um evento, como uma exceção ou um aviso. É assim que um rastreamento de pilha de amostra acompanhado por uma exceção se parece:

Como você pode ver, ele começa imprimindo o nome e a mensagem do erro, seguidos por uma lista de métodos que estavam sendo chamados. Cada chamada de método indica a localização de seu código-fonte e a linha na qual foi invocado. Você pode usar esses dados para navegar pela sua base de código e identificar qual parte do código está causando o erro.
Esta lista de métodos é organizada de forma empilhada. Ele mostra onde sua exceção foi lançada pela primeira vez e como ela se propagou pelas chamadas de método empilhadas. Implementar uma captura para a exceção não permitirá que ela se propague pela pilha e trave seu programa. No entanto, você pode querer deixar erros fatais não detectados para travar o programa em alguns cenários intencionalmente.
Erros vs Exceções
A maioria das pessoas geralmente considera erros e exceções como a mesma coisa. No entanto, é essencial notar uma pequena, mas fundamental diferença entre eles.
Para entender isso melhor, vamos dar um exemplo rápido. Aqui está como você pode definir um erro em JavaScript:
const wrongTypeError = TypeError("Wrong type found, expected character")
E é assim que o objeto wrongTypeError
se torna uma exceção:
throw wrongTypeError
No entanto, a maioria das pessoas tende a usar a forma abreviada que define objetos de erro ao jogá-los:
throw TypeError("Wrong type found, expected character")
Esta é a prática padrão. No entanto, é uma das razões pelas quais os desenvolvedores tendem a misturar exceções e erros. Portanto, conhecer os fundamentos é vital, mesmo que você use atalhos para fazer seu trabalho rapidamente.
Tipos de erros em JavaScript
Há uma variedade de tipos de erros predefinidos em JavaScript. Eles são escolhidos e definidos automaticamente pelo runtime do JavaScript sempre que o programador não trata explicitamente de erros no aplicativo.
Esta seção o guiará por alguns dos tipos mais comuns de erros em JavaScript e entenderá quando e por que eles ocorrem.
Erro de intervalo
Um RangeError é lançado quando uma variável é definida com um valor fora de seu intervalo de valores legais. Geralmente ocorre ao passar um valor como argumento para uma função, e o valor fornecido não está no intervalo dos parâmetros da função. Às vezes, pode ser complicado corrigir ao usar bibliotecas de terceiros mal documentadas, pois você precisa conhecer o intervalo de valores possíveis para que os argumentos passem o valor correto.
Alguns dos cenários comuns em que ocorre RangeError são:
- Tentando criar uma matriz de comprimentos ilegais por meio do construtor Array.
- Passando valores incorretos para métodos numéricos como
toExponential()
,toPrecision()
, toFixed(toFixed()
, etc. - Passando valores ilegais para funções de string como
normalize()
.
Erro de referência
Um ReferenceError ocorre quando algo está errado com a referência de uma variável em seu código. Você pode ter esquecido de definir um valor para a variável antes de usá-la ou pode estar tentando usar uma variável inacessível em seu código. De qualquer forma, percorrer o rastreamento de pilha fornece informações amplas para localizar e corrigir a referência de variável que está com falha.
Alguns dos motivos comuns pelos quais ReferenceErrors ocorrem são:
- Fazendo um erro de digitação em um nome de variável.
- Tentando acessar variáveis com escopo de bloco fora de seus escopos.
- Fazendo referência a uma variável global de uma biblioteca externa (como $ do jQuery) antes de ser carregada.
Erro de sintaxe
Esses erros são um dos mais simples de corrigir, pois indicam um erro na sintaxe do código. Como o JavaScript é uma linguagem de script que é interpretada em vez de compilada, elas são lançadas quando o aplicativo executa o script que contém o erro. No caso de linguagens compiladas, tais erros são identificados durante a compilação. Assim, os binários do aplicativo não são criados até que sejam corrigidos.
Alguns dos motivos comuns pelos quais SyntaxErrors podem ocorrer são:
- Ausência de vírgulas invertidas
- Falta fechar parênteses
- Alinhamento inadequado de chaves ou outros caracteres
É uma boa prática usar uma ferramenta de linting em seu IDE para identificar esses erros antes que eles atinjam o navegador.
TypeError
TypeError é um dos erros mais comuns em aplicativos JavaScript. Este erro é criado quando algum valor não é de um determinado tipo esperado. Alguns dos casos comuns em que ocorre são:
- Invocando objetos que não são métodos.
- Tentando acessar propriedades de objetos nulos ou indefinidos
- Tratando uma string como um número ou vice-versa
Há muito mais possibilidades onde um TypeError pode ocorrer. Veremos alguns exemplos famosos mais tarde e aprenderemos como corrigi-los.
Erro interno
O tipo InternalError é usado quando ocorre uma exceção no mecanismo de tempo de execução do JavaScript. Pode ou não indicar um problema com seu código.
Na maioria das vezes, InternalError ocorre apenas em dois cenários:
- Quando um patch ou uma atualização do runtime do JavaScript carrega um bug que gera exceções (isso acontece muito raramente)
- Quando seu código contém entidades que são muito grandes para o mecanismo JavaScript (por exemplo, muitos casos de comutação, inicializadores de matriz muito grandes, muita recursão)
A abordagem mais apropriada para resolver esse erro é identificar a causa por meio da mensagem de erro e reestruturar a lógica do aplicativo, se possível, para eliminar o pico repentino de carga de trabalho no mecanismo JavaScript.
Erro de URI
URIError ocorre quando uma função de manipulação de URI global, como decodeURIComponent
, é usada ilegalmente. Geralmente indica que o parâmetro passado para a chamada do método não estava em conformidade com os padrões de URI e, portanto, não foi analisado corretamente pelo método.
Diagnosticar esses erros geralmente é fácil, pois você só precisa examinar os argumentos de malformação.
EvalError
Um EvalError ocorre quando ocorre um erro com uma chamada de função eval()
. A função eval()
é usada para executar código JavaScript armazenado em strings. No entanto, como o uso da função eval()
é altamente desencorajado devido a problemas de segurança e as especificações atuais do ECMAScript não lançam mais a classe EvalError
, esse tipo de erro existe simplesmente para manter a compatibilidade com o código JavaScript legado.
Se você estiver trabalhando em uma versão mais antiga do JavaScript, poderá encontrar esse erro. De qualquer forma, é melhor investigar o código executado na chamada da função eval()
para quaisquer exceções.
Criando tipos de erro personalizados
Embora o JavaScript ofereça uma lista adequada de classes de tipo de erro para cobrir a maioria dos cenários, você sempre pode criar um novo tipo de erro se a lista não atender aos seus requisitos. A base dessa flexibilidade está no fato de que o JavaScript permite que você jogue qualquer coisa literalmente com o comando throw
.
Então, tecnicamente, essas declarações são totalmente legais:
throw 8 throw "An error occurred"
No entanto, lançar um tipo de dados primitivo não fornece detalhes sobre o erro, como seu tipo, nome ou o rastreamento de pilha que o acompanha. Para corrigir isso e padronizar o processo de tratamento de erros, a classe Error
foi fornecida. Também é desencorajado o uso de tipos de dados primitivos ao lançar exceções.
Você pode estender a classe Error
para criar sua classe de erro personalizada. Aqui está um exemplo básico de como você pode fazer isso:
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }
E você pode usá-lo da seguinte maneira:
throw ValidationError("Property not found: name")
E você pode identificá-lo usando a palavra-chave instanceof
:
try { validateForm() // code that throws a ValidationError } catch (e) { if (e instanceof ValidationError) // do something else // do something else }
Os 10 erros mais comuns em JavaScript
Agora que você entende os tipos de erros comuns e como criar seus personalizados, é hora de analisar alguns dos erros mais comuns que você enfrentará ao escrever código JavaScript.
1. Erro de intervalo não capturado
Este erro ocorre no Google Chrome em vários cenários. Primeiro, isso pode acontecer se você chamar uma função recursiva e ela não terminar. Você mesmo pode verificar isso no Chrome Developer Console:

Portanto, para resolver esse erro, certifique-se de definir corretamente os casos de borda de sua função recursiva. Outra razão pela qual esse erro acontece é se você passou um valor que está fora do intervalo do parâmetro de uma função. Aqui está um exemplo:

A mensagem de erro geralmente indicará o que está errado com seu código. Depois de fazer as alterações, ele será resolvido.

2. Erro de tipo não capturado: não é possível definir a propriedade
Este erro ocorre quando você define uma propriedade em uma referência indefinida. Você pode reproduzir o problema com este código:
var list list.count = 0
Aqui está a saída que você receberá:

Para corrigir esse erro, inicialize a referência com um valor antes de acessar suas propriedades. Veja como fica quando corrigido:

3. Erro de tipo não capturado: não é possível ler a propriedade
Este é um dos erros mais frequentes em JavaScript. Este erro ocorre quando você tenta ler uma propriedade ou chamar uma função em um objeto indefinido. Você pode reproduzi-lo com muita facilidade executando o seguinte código em um console do desenvolvedor do Chrome:
var func func.call()
Aqui está a saída:

Um objeto indefinido é uma das muitas causas possíveis desse erro. Outra causa proeminente desse problema pode ser uma inicialização incorreta do estado durante a renderização da interface do usuário. Aqui está um exemplo do mundo real de um aplicativo React:
import React, { useState, useEffect } from "react"; const CardsList = () => { const [state, setState] = useState(); useEffect(() => { setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000); }, []); return ( <> {state.items.map((item) => ( <li key={item}>{item}</li> ))} </> ); }; export default CardsList;
O aplicativo inicia com um contêiner de estado vazio e é fornecido com alguns itens após um atraso de 2 segundos. O atraso é implementado para imitar uma chamada de rede. Mesmo que sua rede seja super rápida, você ainda enfrentará um pequeno atraso devido ao qual o componente será renderizado pelo menos uma vez. Se você tentar executar este aplicativo, receberá o seguinte erro:

Isso ocorre porque, no momento da renderização, o contêiner de estado está indefinido; assim, não existe nenhum items
de propriedade nele. Corrigir este erro é fácil. Você só precisa fornecer um valor padrão inicial para o contêiner de estado.
// ... const [state, setState] = useState({items: []}); // ...
Agora, após o atraso definido, seu aplicativo mostrará uma saída semelhante:

A correção exata em seu código pode ser diferente, mas a essência aqui é sempre inicializar suas variáveis corretamente antes de usá-las.
4. TypeError: 'undefined' não é um objeto
Este erro ocorre no Safari quando você tenta acessar as propriedades ou chamar um método em um objeto indefinido. Você pode executar o mesmo código acima para reproduzir o erro você mesmo.

A solução para este erro também é a mesma — certifique-se de que você inicializou suas variáveis corretamente e elas não estão indefinidas quando uma propriedade ou método é acessado.
5. TypeError: null não é um objeto
Isso é, novamente, semelhante ao erro anterior. Isso ocorre no Safari, e a única diferença entre os dois erros é que este é lançado quando o objeto cuja propriedade ou método está sendo acessado é null
em vez de undefined
. Você pode reproduzir isso executando o seguinte trecho de código:
var func = null func.call()
Aqui está a saída que você receberá:

Como null
é um valor definido explicitamente para uma variável e não atribuído automaticamente pelo JavaScript. Este erro pode ocorrer apenas se você estiver tentando acessar uma variável que você mesmo definiu como null
. Então, você precisa revisitar seu código e verificar se a lógica que você escreveu está correta ou não.
6. TypeError: Não é possível ler a propriedade 'comprimento'
Este erro ocorre no Chrome quando você tenta ler o comprimento de um objeto null
ou undefined
. A causa desse problema é semelhante aos problemas anteriores, mas ocorre com bastante frequência durante o manuseio de listas; por isso merece uma menção especial. Veja como você pode reproduzir o problema:

No entanto, nas versões mais recentes do Chrome, esse erro é relatado como Uncaught TypeError: Cannot read properties of undefined
. É assim que parece agora:

A correção, novamente, é garantir que o objeto cujo comprimento você está tentando acessar exista e não esteja definido como null
.
7. TypeError: 'undefined' não é uma função
Esse erro ocorre quando você tenta invocar um método que não existe em seu script ou existe, mas não pode ser referenciado no contexto de chamada. Esse erro geralmente ocorre no Google Chrome e você pode resolvê-lo verificando a linha de código que gera o erro. Se você encontrar um erro de digitação, corrija-o e verifique se ele resolve seu problema.
Se você usou a palavra-chave de auto-referência this
em seu código, esse erro pode surgir se this
não estiver vinculado adequadamente ao seu contexto. Considere o seguinte código:
function showAlert() { alert("message here") } document.addEventListener("click", () => { this.showAlert(); })
Se você executar o código acima, ele lançará o erro que discutimos. Isso acontece porque a função anônima passada como ouvinte do evento está sendo executada no contexto do document
.
Em contraste, a função showAlert
é definida no contexto da window
.
Para resolver isso, você deve passar a referência adequada para a função vinculando-a com o método bind()
:
document.addEventListener("click", this.showAlert.bind(this))
8. ReferenceError: o evento não está definido
Este erro ocorre quando você tenta acessar uma referência não definida no escopo de chamada. Isso geralmente acontece ao manipular eventos, pois eles geralmente fornecem uma referência chamada event
na função de retorno de chamada. Este erro pode ocorrer se você esquecer de definir o argumento do evento nos parâmetros da sua função ou se o digitar incorretamente.
Esse erro pode não ocorrer no Internet Explorer ou no Google Chrome (já que o IE oferece uma variável de evento global e o Chrome anexa a variável de evento automaticamente ao manipulador), mas pode ocorrer no Firefox. Portanto, é aconselhável ficar atento a esses pequenos erros.

9. TypeError: Atribuição à variável constante
Este é um erro que surge por descuido. Se você tentar atribuir um novo valor a uma variável constante, você encontrará esse resultado:

Embora pareça fácil de corrigir agora, imagine centenas de declarações de variáveis e uma delas erroneamente definida como const
em vez de let
! Ao contrário de outras linguagens de script como PHP, há uma diferença mínima entre o estilo de declarar constantes e variáveis em JavaScript. Portanto, é aconselhável verificar suas declarações antes de tudo quando você se deparar com esse erro. Você também pode encontrar esse erro se esquecer que a referida referência é uma constante e usá-la como uma variável. Isso indica descuido ou uma falha na lógica do seu aplicativo. Certifique-se de verificar isso ao tentar corrigir esse problema.
10. (desconhecido): erro de script
Um erro de script ocorre quando um script de terceiros envia um erro ao seu navegador. Este erro é seguido por (desconhecido) porque o script de terceiros pertence a um domínio diferente do seu aplicativo. O navegador oculta outros detalhes para evitar o vazamento de informações confidenciais do script de terceiros.
Você não pode resolver esse erro sem conhecer os detalhes completos. Veja o que você pode fazer para obter mais informações sobre o erro:
- Adicione o atributo
crossorigin
na tag de script. - Defina o cabeçalho
Access-Control-Allow-Origin
correto no servidor que hospeda o script. - [Opcional] Se você não tiver acesso ao servidor que hospeda o script, considere usar um proxy para retransmitir sua solicitação ao servidor e de volta ao cliente com os cabeçalhos corretos.
Depois de acessar os detalhes do erro, você pode se preparar para corrigir o problema, que provavelmente será com a biblioteca de terceiros ou a rede.
Como identificar e prevenir erros em JavaScript
Embora os erros discutidos acima sejam os mais comuns e frequentes em JavaScript, você encontrará, confiar em alguns exemplos nunca será suficiente. É vital entender como detectar e prevenir qualquer tipo de erro em um aplicativo JavaScript ao desenvolvê-lo. Aqui está como você pode lidar com erros em JavaScript.
Lançar e capturar erros manualmente
A maneira mais fundamental de lidar com erros que foram gerados manualmente ou pelo tempo de execução é capturá-los. Como a maioria das outras linguagens, o JavaScript oferece um conjunto de palavras-chave para lidar com erros. É essencial conhecer cada um deles em profundidade antes de começar a lidar com erros em seu aplicativo JavaScript.
jogar
A primeira e mais básica palavra-chave do conjunto é throw
. Como é evidente, a palavra-chave throw é usada para gerar erros para criar exceções no tempo de execução do JavaScript manualmente. Já discutimos isso no início do artigo, e aqui está a essência do significado dessa palavra-chave:
- Você pode
throw
qualquer coisa, incluindo números, strings e objetosError
. - No entanto, não é aconselhável lançar tipos de dados primitivos, como strings e números, pois eles não carregam informações de depuração sobre os erros.
- Exemplo:
throw TypeError("Please provide a string")
experimentar
A palavra-chave try
é usada para indicar que um bloco de código pode lançar uma exceção. Sua sintaxe é:
try { // error-prone code here }
É importante observar que um bloco catch
deve sempre seguir o bloco try
para tratar os erros de forma eficaz.
pegar
A palavra-chave catch
é usada para criar um bloco catch. Este bloco de código é responsável por tratar os erros que o bloco try
à direita captura. Aqui está sua sintaxe:
catch (exception) { // code to handle the exception here }
E é assim que você implementa os blocos try
e catch
juntos:
try { // business logic code } catch (exception) { // error handling code }
Ao contrário de C++ ou Java, você não pode anexar vários blocos catch
a um bloco try
em JavaScript. Isso significa que você não pode fazer isso:
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } } catch (exception) { if (exception instanceof RangeError) { // do something } }
Em vez disso, você pode usar uma instrução if...else
ou uma instrução switch case dentro do único bloco catch para lidar com todos os possíveis casos de erro. Ficaria assim:
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } else if (exception instanceof RangeError) { // do something else } }
finalmente
A palavra-chave finally
é usada para definir um bloco de código que é executado após o tratamento de um erro. Este bloco é executado após os blocos try e catch.
Além disso, o bloco finally será executado independentemente do resultado dos outros dois blocos. Isso significa que mesmo que o bloco catch não possa tratar o erro inteiramente ou um erro seja lançado no bloco catch, o interpretador executará o código no bloco finally antes que o programa trave.
Para ser considerado válido, o bloco try em JavaScript precisa ser seguido por um bloco catch ou finally. Sem nenhum deles, o interpretador gerará um SyntaxError. Portanto, certifique-se de seguir seus blocos try com pelo menos um deles ao lidar com erros.
Lidar com erros globalmente com o método onerror()
O método onerror()
está disponível para todos os elementos HTML para lidar com quaisquer erros que possam ocorrer com eles. Por exemplo, se uma tag img
não puder encontrar a imagem cujo URL está especificado, ela acionará seu método onerror para permitir que o usuário lide com o erro.
Normalmente, você forneceria outro URL de imagem na chamada onerror para a tag img
retornar. É assim que você pode fazer isso via JavaScript:
const image = document.querySelector("img") image.onerror = (event) => { console.log("Error occurred: " + event) }
No entanto, você pode usar esse recurso para criar um mecanismo global de tratamento de erros para seu aplicativo. Veja como você pode fazer isso:
window.onerror = (event) => { console.log("Error occurred: " + event) }
Com esse manipulador de eventos, você pode se livrar dos vários blocos try...catch
espalhados pelo seu código e centralizar o tratamento de erros do seu aplicativo semelhante ao tratamento de eventos. Você pode anexar vários manipuladores de erro à janela para manter o Princípio de Responsabilidade Única dos princípios de design SOLID. O interpretador percorrerá todos os manipuladores até chegar ao apropriado.
Erros de passagem por meio de retornos de chamada
Enquanto as funções simples e lineares permitem que o tratamento de erros permaneça simples, os retornos de chamada podem complicar o assunto.
Considere o seguinte trecho de código:
Precisa de uma solução de hospedagem que lhe dê uma vantagem competitiva? Kinsta cobre você com velocidade incrível, segurança de última geração e dimensionamento automático. Confira nossos planos
const calculateCube = (number, callback) => { setTimeout(() => { const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) calculateCube(4, callback)
A função acima demonstra uma condição assíncrona na qual uma função leva algum tempo para processar as operações e retorna o resultado posteriormente com a ajuda de um retorno de chamada.
Se você tentar inserir uma string em vez de 4 na chamada da função, obterá NaN
como resultado.
Isso precisa ser tratado adequadamente. Veja como:
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") throw new Error("Numeric argument is expected") const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) try { calculateCube(4, callback) } catch (e) { console.log(e) }
Isso deve resolver o problema idealmente. No entanto, se você tentar passar uma string para a chamada da função, receberá isto:

Mesmo que você tenha implementado um bloco try-catch ao chamar a função, ele ainda diz que o erro não foi detectado. O erro é lançado após a execução do bloco catch devido ao atraso de tempo limite.
Isso pode ocorrer rapidamente em chamadas de rede, onde ocorrem atrasos inesperados. Você precisa cobrir esses casos ao desenvolver seu aplicativo.
Veja como você pode lidar com erros corretamente em retornos de chamada:
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") { callback(new TypeError("Numeric argument is expected")) return } const cube = number * number * number callback(null, cube) }, 2000) } const callback = (error, result) => { if (error !== null) { console.log(error) return } console.log(result) } try { calculateCube('hey', callback) } catch (e) { console.log(e) }
Agora, a saída no console será:

Isso indica que o erro foi tratado adequadamente.
Lidar com erros em promessas
A maioria das pessoas tende a preferir promessas para lidar com atividades assíncronas. As promessas têm outra vantagem — uma promessa rejeitada não encerra seu script. No entanto, você ainda precisa implementar um bloco catch para lidar com erros nas promessas. Para entender isso melhor, vamos reescrever a função calculateCube()
usando Promises:
const delay = ms => new Promise(res => setTimeout(res, ms)); const calculateCube = async (number) => { if (typeof number !== "number") throw Error("Numeric argument is expected") await delay(5000) const cube = number * number * number return cube } try { calculateCube(4).then(r => console.log(r)) } catch (e) { console.log(e) }
O tempo limite do código anterior foi isolado na função de delay
para compreensão. Se você tentar inserir uma string em vez de 4, a saída será semelhante a esta:

Novamente, isso se deve ao fato de a Promise
lançar o erro após a execução de todo o resto. A solução para este problema é simples. Simplesmente adicione uma chamada catch()
à cadeia de promessas assim:
calculateCube("hey") .then(r => console.log(r)) .catch(e => console.log(e))
Agora a saída será:

Você pode observar como é fácil lidar com erros com promessas. Além disso, você pode encadear um bloco finally()
e a chamada de promessa para adicionar código que será executado após a conclusão do tratamento de erros.
Alternativamente, você também pode lidar com erros em promessas usando a técnica tradicional try-catch-finally. Veja como sua chamada de promessa ficaria nesse caso:
try { let result = await calculateCube("hey") console.log(result) } catch (e) { console.log(e) } finally { console.log('Finally executed") }
No entanto, isso funciona apenas dentro de uma função assíncrona. Portanto, a maneira mais preferida de lidar com erros em promessas é encadear catch
e, finally
, a chamada de promessa.
throw/catch vs onerror() vs Callbacks vs Promises: Qual é o melhor?
Com quatro métodos à sua disposição, você deve saber escolher o mais adequado em qualquer caso de uso. Veja como você pode decidir por si mesmo:
jogar/pegar
Você usará esse método na maioria das vezes. Certifique-se de implementar condições para todos os erros possíveis dentro de seu bloco catch e lembre-se de incluir um bloco finally se precisar executar algumas rotinas de limpeza de memória após o bloco try.
No entanto, muitos blocos try/catch podem dificultar a manutenção do seu código. Se você se encontrar nessa situação, talvez queira lidar com erros por meio do manipulador global ou do método de promessa.
Ao decidir entre os blocos try/catch assíncronos e catch()
da promessa, é aconselhável usar os blocos try/catch assíncronos, pois eles tornarão seu código linear e fácil de depurar.
onerror()
É melhor usar o método onerror()
quando você sabe que seu aplicativo precisa lidar com muitos erros e eles podem ser bem espalhados por toda a base de código. O método onerror
permite que você trate os erros como se fossem apenas mais um evento manipulado pelo seu aplicativo. Você pode definir vários manipuladores de erros e anexá-los à janela do seu aplicativo na renderização inicial.
No entanto, você também deve lembrar que o método onerror()
pode ser desnecessariamente desafiador para configurar em projetos menores com um escopo menor de erro. Se você tiver certeza de que seu aplicativo não gerará muitos erros, o método tradicional de lançamento/captura funcionará melhor para você.
Retornos e promessas
O tratamento de erros em retornos de chamada e promessas difere devido ao design e à estrutura do código. No entanto, se você escolher entre esses dois antes de escrever seu código, seria melhor ir com promessas.
Isso ocorre porque as promessas têm uma construção embutida para encadear um bloco catch()
e finally()
para lidar com erros facilmente. Este método é mais fácil e limpo do que definir argumentos adicionais/reutilizar argumentos existentes para lidar com erros.
Keep Track of Changes With Git Repositories
Many errors often arise due to manual mistakes in the codebase. While developing or debugging your code, you might end up making unnecessary changes that may cause new errors to appear in your codebase. Automated testing is a great way to keep your code in check after every change. However, it can only tell you if something's wrong. If you don't take frequent backups of your code, you'll end up wasting time trying to fix a function or a script that was working just fine before.
This is where git plays its role. With a proper commit strategy, you can use your git history as a backup system to view your code as it evolved through the development. You can easily browse through your older commits and find out the version of the function working fine before but throwing errors after an unrelated change.
You can then restore the old code or compare the two versions to determine what went wrong. Modern web development tools like GitHub Desktop or GitKraken help you to visualize these changes side by side and figure out the mistakes quickly.
A habit that can help you make fewer errors is running code reviews whenever you make a significant change to your code. If you're working in a team, you can create a pull request and have a team member review it thoroughly. This will help you use a second pair of eyes to spot out any errors that might have slipped by you.
Best Practices for Handling Errors in JavaScript
The above-mentioned methods are adequate to help you design a robust error handling approach for your next JavaScript application. However, it would be best to keep a few things in mind while implementing them to get the best out of your error-proofing. Here are some tips to help you.
1. Use Custom Errors When Handling Operational Exceptions
We introduced custom errors early in this guide to give you an idea of how to customize the error handling to your application's unique case. It's advisable to use custom errors wherever possible instead of the generic Error
class as it provides more contextual information to the calling environment about the error.
On top of that, custom errors allow you to moderate how an error is displayed to the calling environment. This means that you can choose to hide specific details or display additional information about the error as and when you wish.
You can go so far as to format the error contents according to your needs. This gives you better control over how the error is interpreted and handled.
2. Do Not Swallow Any Exceptions
Even the most senior developers often make a rookie mistake — consuming exceptions levels deep down in their code.
You might come across situations where you have a piece of code that is optional to run. If it works, great; if it doesn't, you don't need to do anything about it.
In these cases, it's often tempting to put this code in a try block and attach an empty catch block to it. However, by doing this, you'll leave that piece of code open to causing any kind of error and getting away with it. This can become dangerous if you have a large codebase and many instances of such poor error management constructs.
The best way to handle exceptions is to determine a level on which all of them will be dealt and raise them until there. This level can be a controller (in an MVC architecture app) or a middleware (in a traditional server-oriented app).
This way, you'll get to know where you can find all the errors occurring in your app and choose how to resolve them, even if it means not doing anything about them.
3. Use a Centralized Strategy for Logs and Error Alerts
Logging an error is often an integral part of handling it. Those who fail to develop a centralized strategy for logging errors may miss out on valuable information about their app's usage.
An app's event logs can help you figure out crucial data about errors and help to debug them quickly. If you have proper alerting mechanisms set up in your app, you can know when an error occurs in your app before it reaches a large section of your user base.
It's advisable to use a pre-built logger or create one to suit your needs. You can configure this logger to handle errors based on their levels (warning, debug, info, etc.), and some loggers even go so far as to send logs to remote logging servers immediately. This way, you can watch how your application's logic performs with active users.
4. Notify Users About Errors Appropriately
Another good point to keep in mind while defining your error handling strategy is to keep the user in mind.
All errors that interfere with the normal functioning of your app must present a visible alert to the user to notify them that something went wrong so the user can try to work out a solution. If you know a quick fix for the error, such as retrying an operation or logging out and logging back in, make sure to mention it in the alert to help fix the user experience in real-time.
In the case of errors that don't cause any interference with the everyday user experience, you can consider suppressing the alert and logging the error to a remote server for resolving later.
5. Implement a Middleware (Node.js)
The Node.js environment supports middlewares to add functionalities to server applications. You can use this feature to create an error-handling middleware for your server.
The most significant benefit of using middleware is that all of your errors are handled centrally in one place. You can choose to enable/disable this setup for testing purposes easily.
Here's how you can create a basic middleware:
const logError = err => { console.log("ERROR: " + String(err)) } const errorLoggerMiddleware = (err, req, res, next) => { logError(err) next(err) } const returnErrorMiddleware = (err, req, res, next) => { res.status(err.statusCode || 500) .send(err.message) } module.exports = { logError, errorLoggerMiddleware, returnErrorMiddleware }
Você pode usar esse middleware em seu aplicativo assim:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware') app.use(errorLoggerMiddleware) app.use(returnErrorMiddleware)
Agora você pode definir a lógica personalizada dentro do middleware para tratar os erros adequadamente. Você não precisa mais se preocupar em implementar construções de tratamento de erros individuais em toda a sua base de código.
6. Reinicie seu aplicativo para lidar com erros do programador (Node.js)
Quando os aplicativos Node.js encontram erros do programador, eles podem não necessariamente lançar uma exceção e tentar fechar o aplicativo. Esses erros podem incluir problemas decorrentes de erros do programador, como alto consumo de CPU, inchaço de memória ou vazamentos de memória. A melhor maneira de lidar com isso é reiniciar o aplicativo normalmente, travando-o por meio do modo de cluster Node.js ou de uma ferramenta exclusiva como PM2. Isso pode garantir que o aplicativo não falhe com a ação do usuário, apresentando uma experiência de usuário terrível.
7. Capturar todas as exceções não detectadas (Node.js)
Você nunca pode ter certeza de que cobriu todos os erros possíveis que podem ocorrer em seu aplicativo. Portanto, é essencial implementar uma estratégia de fallback para capturar todas as exceções não capturadas do seu aplicativo.
Veja como você pode fazer isso:
process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // other handling mechanisms })
Você também pode identificar se o erro que ocorreu é uma exceção padrão ou um erro operacional personalizado. Com base no resultado, você pode sair do processo e reiniciá-lo para evitar um comportamento inesperado.
8. Capturar todas as rejeições de promessas não tratadas (Node.js)
Semelhante a como você nunca pode cobrir todas as exceções possíveis, há uma grande chance de você perder o tratamento de todas as possíveis rejeições de promessa. No entanto, ao contrário das exceções, as rejeições de promessas não geram erros.
Portanto, uma promessa importante que foi rejeitada pode passar despercebida e deixar seu aplicativo aberto à possibilidade de se deparar com um comportamento inesperado. Portanto, é crucial implementar um mecanismo de fallback para lidar com a rejeição de promessas.
Veja como você pode fazer isso:
const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error)) } process.on('unhandledRejection', callback)
Resumo
Como qualquer outra linguagem de programação, os erros são bastante frequentes e naturais em JavaScript. Em alguns casos, você pode até precisar lançar erros intencionalmente para indicar a resposta correta para seus usuários. Portanto, entender sua anatomia e tipos é muito crucial.
Além disso, você precisa estar equipado com as ferramentas e técnicas certas para identificar e evitar que erros desativem seu aplicativo.
Na maioria dos casos, uma estratégia sólida para lidar com erros com execução cuidadosa é suficiente para todos os tipos de aplicativos JavaScript.
Existem outros erros de JavaScript que você ainda não conseguiu resolver? Alguma técnica para lidar com erros JS de forma construtiva? Deixe-nos saber nos comentários abaixo!