A lei de Murphy diz que tudo que pode dar errado eventualmente dará errado. Isto se aplica um pouco bem demais no mundo da programação. Se você criar um aplicativo, é provável que você crie bugs e outros problemas. Erros no JavaScript são um desses problemas comuns!

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 o JavaScript, de todas as linguagens de programação, é notório por seu design de manipulação de erros médio.

Se você estiver construindo um aplicativo JavaScript, há uma grande chance de você mexer com tipos de dados em um ponto ou outro. Se não for isso, então você pode acabar substituindo um operador indefinido por um operador nulo ou triplo igual (===) por um operador duplo igual (==).

É humano apenas cometer erros. É por isso que vamos mostrar a você tudo o que você precisa saber sobre como lidar com erros no JavaScript.

Este artigo irá guiá-lo através dos erros básicos no JavaScript e explicar os vários erros que você pode encontrar. Você aprenderá então como identificar e corrigir esses erros. Há também algumas dicas para lidar com os erros de forma eficaz em ambientes de produção.

Sem mais delongas, vamos começar!

Confira nosso guia em vídeo para lidar com erros de JavaScript

O que são erros no JavaScript?

Os erros na programação referem-se a situações que não permitem que um programa funcione normalmente. Pode acontecer quando um programa não sabe como lidar com o trabalho em questão, como quando se tenta abrir um arquivo inexistente ou alcançar um endpoint API baseado na web enquanto não há conectividade de rede.

Estas situações empurram o programa para lançar erros ao usuário, afirmando que ele não sabe como proceder. O programa coleta o máximo de informações possíveis sobre o erro e então relata que não pode avançar.

Programadores inteligentes tentam prever e cobrir estes cenários para que o usuário não tenha que descobrir uma mensagem de erro técnico como “404” independentemente. Ao invés disso, eles mostram uma mensagem muito mais compreensível: “A página não pôde ser encontrada”

Os erros no JavaScript são objetos mostrados sempre que ocorre um erro de programação. Estes objetos contêm amplas informações sobre o tipo do erro, a declaração que causou o erro e o traço da 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.

Os erros no JavaScript carregam certas propriedades padrão e personalizadas que ajudam a entender a causa e os efeitos do erro. Por padrão, os erros no JavaScript contêm três propriedades:

  1. message: Um valor de string que carrega a mensagem de erro
  2. name: O tipo de erro que ocorreu (Vamos mergulhar fundo nisto na próxima seção)
  3. stack: O rastreamento da pilha do código executado quando o erro ocorreu.

Além disso, erros também podem conter propriedades como columnNumber, lineNumber, fileName, etc., para descrever melhor o erro. Entretanto, essas propriedades não são padrão e podem ou não estar presentes em todos os objetos de erro gerados a partir do seu aplicativo JavaScript.

Entendendo o rastreamento de pilha (stacktrace)

Um rastreamento de pilha é a lista de chamadas de método em que um programa estava quando um evento como uma exceção ou um aviso ocorre. É assim que uma amostra de rastreamento de pilha acompanhada por uma exceção se parece:

Exemplo de um Stack Trace
Exemplo de um Stack Trace

Como você pode ver, ele começa imprimindo o nome e a mensagem de erro, seguido por uma lista de métodos que estavam sendo chamados. Cada chamada de método declara a localização do seu código fonte e a linha na qual ele foi invocado. Você pode usar estes dados para navegar através de 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. Ela mostra onde a sua exceção foi lançada pela primeira vez e como ela se propagou através das chamadas do método empilhado. A implementação de um catch para a exceção não deixará que ela se propague através da pilha e trave seu programa. No entanto, você pode querer deixar erros fatais sem causar danos para travar o programa em alguns cenários intencionalmente.

Erros vs Exceções

A maioria das pessoas normalmente considera erros e exceções como a mesma coisa. Entretanto, é 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 no JavaScript:

const wrongTypeError = TypeError("Wrong type found, expected character")

E é assim que o wrongTypeError objeto se torna uma exceção:

throw wrongTypeError

Entretanto, a maioria das pessoas tende a usar a forma abreviada que define os objetos de erro enquanto os atira:

throw TypeError("Wrong type found, expected character")

Esta é uma prática padrão. Entretanto, é uma das razões pelas quais os desenvolvedores tendem a misturar exceções e erros. Portanto, conhecer os fundamentos é vital, mesmo que você use o uso de taquigrafia para fazer o seu trabalho rapidamente.

Tipos de erros no JavaScript

Há uma gama de tipos de erros predefinidos no JavaScript. Eles são automaticamente escolhidos e definidos pelo JavaScript runtime sempre que o programador não lida explicitamente com erros no aplicativo.

Esta seção vai guiá-lo através de alguns dos tipos mais comuns de erros no JavaScript e entender quando e porque eles ocorrem.

RangeError

Um RangeError é lançado quando uma variável é definida com um valor fora de sua faixa de valores legais. Normalmente ocorre quando se passa um valor como argumento para uma função, e o valor dado não se encontra no intervalo dos parâmetros da função. Às vezes pode ser complicado corrigir quando se usa bibliotecas de terceiros mal documentadas, já que você precisa conhecer o intervalo de valores possíveis para que os argumentos passem no valor correto.

Alguns dos cenários comuns em que o RangeError ocorre são:

  • Tentando criar um array de comprimentos ilegais através do construtor do Array.
  • Passando valores ruins para métodos numéricos como toExponential(), toPrecision(), toFixed(), etc.
  • Passando valores ilegais para funções de string como normalize().

ReferenceError

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, passar pelo rastreamento de pilha fornece amplas informações para encontrar e corrigir a referência da variável que está em falta.

Algumas das razões comuns pelas quais os ReferenceErrors ocorrem são:

  • Fazendo um erro de digitação em um nome da variável.
  • Tentando acessar variáveis em bloco fora de seus escopos.
  • Referenciar uma variável global de uma biblioteca externa (como $ de jQuery) antes que ela seja carregada.

SintaxError

Estes erros são dos mais simples de corrigir, pois indicam um erro na sintaxe do código. Como o JavaScript é uma linguagem de script que é interpretada ao invés de compilada, estes são lançados 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 da app não são criados até que estes sejam corrigidos.

Algumas das razões comuns pelas quais os SyntaxErrors podem ocorrer são:

  • Faltando vírgulas invertidas
  • Parênteses de fechamento ausentes
  • Alinhamento impróprio de suportes de caracóis ou outros caracteres

É uma boa prática usar uma ferramenta de impressão em sua IDE para identificar tais erros para você antes que eles cheguem ao navegador.

TypeError

TypeError é um dos erros mais comuns em aplicativos JavaScript. Este erro é criado quando algum valor não se revela de um tipo específico esperado. Alguns dos casos comuns quando ele ocorre são:

  • Invocando objetos que não são métodos.
  • Tentativa de acessar propriedades de objetos nulos ou indefinidos
  • Tratar um string como um número ou vice versa

Há muito mais possibilidades onde um TypeError pode ocorrer. Vamos olhar para algumas instâncias famosas mais tarde e aprender como consertá-las.

InternalError

O tipo InternalError é usado quando ocorre uma exceção no motor em tempo de execução JavaScript. Ele pode ou não indicar um problema com o seu código.

Na maioria das vezes, o InternalError ocorre apenas em dois cenários:

  • Quando um patch ou uma atualização do tempo de execução do JavaScript traz um bug que lança 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 switch, inicializadores de array muito grandes, demasiada recorrência)

A abordagem mais apropriada para resolver este erro é identificar a causa através da mensagem de erro e reestruturar sua lógica do aplicativo, se possível, para eliminar o pico repentino de carga de trabalho no mecanismo JavaScript.

URIError

O URIError ocorre quando uma função global de manuseio de URI, como decodeURIComponent, é usada ilegalmente. Ele normalmente indica que o parâmetro passado para a chamada do método não estava de acordo com os padrões URI e, portanto, não foi analisado corretamente pelo método.

Diagnosticar esses erros geralmente é fácil, uma vez que você só precisa examinar os argumentos para a má-formaçã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. Entretanto, como o uso da função eval() é altamente desencorajado devido a problemas de segurança e as especificações atuais do ECMAScript não jogam mais a classe EvalError, este tipo de erro existe simplesmente para manter a compatibilidade retroativa com o código JavaScript antigo.

Se você estiver trabalhando em uma versão antiga do JavaScript, você pode encontrar este erro. Em qualquer caso, é melhor investigar o código executado na chamada de função eval() para qualquer exceção.

Criando tipos de erros personalizados

Enquanto o JavaScript oferece 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 satisfizer suas exigências. A base desta flexibilidade está no fato de que o JavaScript permite que você lance qualquer coisa literalmente com o comando throw.

Portanto, tecnicamente, estas declarações são inteiramente legais:

throw 8
throw "An error occurred"

Entretanto, lançar um tipo de dado primitivo não fornece detalhes sobre o erro, tais como seu tipo, nome ou o rastreamento de pilha que o acompanha. Para corrigir isso e padronizar o processo de gerenciamento de erros, a classe Error foi fornecida. Também é desencorajado o uso de tipos de dados primitivos enquanto se lançam 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 então 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 no JavaScript

Agora que você entende os tipos de erros comuns e como criar seus erros personalizados, é hora de olhar para alguns dos erros mais comuns que você vai enfrentar ao escrever código JavaScript.

1. Uncaught RangeError

Este erro ocorre no Google Chrome em alguns cenários diferentes. Primeiro, ele pode acontecer se você chamar uma função recursiva e ela não terminar. Você mesmo pode verificar isso no Console do Desenvolvedor do Chrome:

Exemplo do RangeError com uma chamada recursiva de função.
Exemplo do RangeError com uma chamada recursiva de função.

Portanto, para resolver tal erro, certifique-se de definir corretamente os casos limite da sua função recursiva. Outra razão pela qual este erro ocorre é que você passou um valor que está fora da faixa de parâmetros de uma função. Aqui está um exemplo:

Exemplo do RangeError com a chamada toExponential().
Exemplo do RangeError com a chamada toExponential().

A mensagem de erro normalmente indicará o que está errado com o seu código. Uma vez que você fizer as mudanças, ele será resolvido.

Resultado para a chamada de função toExponential().
Resultado para a chamada de função toExponential().

2. Uncaught TypeError: Cannot set property

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

Este é o resultado que você receberá:

Exemplo do TypeError.
Exemplo do TypeError.

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

Como resolver o TypeError.
Como resolver o TypeError.

3. Uncaught TypeError: Cannot read property

Este é um dos erros mais frequentes no JavaScript. Este erro ocorre quando você tenta ler uma propriedade ou chamar uma função em um objeto indefinido. Você pode reproduzi-lo muito facilmente executando o seguinte código em um console do Chrome Developer:

var func
func.call()

Aqui está o resultado:

TypeError exemplo com função indefinida.
TypeError exemplo com função indefinida.

Um objeto indefinido é uma das muitas causas possíveis deste erro. Outra causa proeminente desta questão pode ser uma inicialização imprópria do estado enquanto se processa a IU. 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 começa com um contêiner de estado vazio e é fornecido com alguns itens após um atraso de 2 segundos. O atraso é colocado em prática 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 irá render pelo menos uma vez. Se você tentar executar este aplicativo, você receberá o seguinte erro:

TypeError stack trace em um navegador.
TypeError stack trace em um navegador.

Isto porque, no momento da renderização, o contêiner estatal é indefinido; portanto, não existe nenhuma propriedade items sobre ele. A correção deste erro é fácil. Você só precisa fornecer um valor inicial padrão para o contêiner estadual.

// ...
const [state, setState] = useState({items: []});
// ...

Agora, após o atraso definido, seu aplicativo mostrará uma resultado semelhante a este aqui:

Resultado do código.
Resultado do código.

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’ is not an object

Este erro ocorre no Safari quando você tenta acessar as propriedades de ou chamar um método sobre um objeto indefinido. Você pode executar o mesmo código de cima para reproduzir o erro você mesmo.

TypeError exemplo com função indefinida.
TypeError exemplo com função indefinida.

A solução para este erro também é a mesma – certifique-se de que você iniciou suas variáveis corretamente e que elas não estejam indefinidas quando uma propriedade ou método é acessado.

5. TypeError: null is not an object

Isto é, novamente, similar ao erro anterior. 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 ao invés de undefined. Você pode reproduzir isto executando o seguinte trecho de código:

var func = null

func.call()

Este é o resultado que você obterá:

TypeError exemplo com função nula.
TypeError exemplo com função nula.

Como null é um valor explicitamente definido para uma variável e não atribuído automaticamente pelo JavaScript. Este erro só pode ocorrer se você estiver tentando acessar uma variável que você mesmo definiu em 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: Cannot read property ‘length’

Este erro ocorre no Chrome quando você tenta ler o comprimento de um objeto null ou undefined. A causa desta edição é semelhante às edições anteriores, mas ela ocorre com bastante frequência enquanto manipula listas; por isso merece uma menção especial. Aqui está como você pode reproduzir o problema:

TypeError exemplo com um objeto indefinido.
TypeError exemplo com um objeto indefinido.

No entanto, nas versões mais recentes do Chrome, este erro é relatado como Uncaught TypeError: Cannot read properties of undefined. Esta é a aparência atual:

TypeError exemplo com um objeto indefinido nas versões mais recentes do Chrome.
TypeError exemplo com um objeto indefinido nas versões mais recentes do Chrome.

A correção, novamente, é garantir que o objeto cujo comprimento você está tentando acessar exista e não esteja configurado para null.

7. TypeError: ‘undefined’ is not a function

Este 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 da chamada. Este erro geralmente ocorre no Google Chrome, e você pode resolvê-lo verificando a linha de código que lança o erro. Se você encontrar um erro de digitação, corrija e verifique se ele resolve o seu problema.

Se você usou a palavra-chave auto-referência this em seu código, este erro pode surgir se this não estiver adequadamente vinculado 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 vai lançar o erro que discutimos. Isso acontece porque a função anônima passada como o 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 devida referência à função vinculando-a com o método bind():

document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError: event is not defined

Este erro ocorre quando você tenta acessar uma referência não definida no escopo da chamada. Isto geralmente acontece quando se trata de eventos, já que eles freqüentemente fornecem a você uma referência chamada event na função callbacks. Este erro pode ocorrer se você esquecer de definir o argumento do evento nos parâmetros da sua função ou escrevê-lo incorretamente.

Este 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 ele pode ocorrer no Firefox. Portanto, é aconselhável ficar de olho em erros tão pequenos.

9. TypeError: Assignment to constant variable

Este é um erro que surge por descuido. Se você tentar atribuir um novo valor a uma variável constante, você será recebido com tal resultado:

TypeError exemplo com atribuição de objeto constante.
TypeError exemplo com atribuição de objeto constante.

Embora pareça fácil de corrigir agora mesmo, imagine centenas dessas declarações variáveis e uma delas erroneamente definida como const ao invés de let! Ao contrário de outras linguagens de script como PHP, há uma diferença mínima entre o estilo de declaração de constantes e variáveis em JavaScript. Portanto, é aconselhável verificar suas declarações antes de tudo quando você enfrenta este erro. Você também pode se deparar com este erro se você esquecer que a referida referência é uma constante e usá-la como variável. Isto indica descuido ou uma falha na lógica do seu aplicativo. Certifique-se de verificar isto ao tentar corrigir este problema.

10. (unknown): Script error

Um erro de script ocorre quando um script de terceiros envia um erro para o seu navegador. Este erro é seguido por (desconhecido) porque o script de terceiros pertence a um domínio diferente do seu aplicativo. O navegador esconde outros detalhes para evitar vazamento de informações confidenciais do script de terceiros.

Você não pode resolver este erro sem conhecer os detalhes completos. Aqui está o que você pode fazer para obter mais informações sobre o erro:

  1. Adicione o atributo crossorigin na tag do script.
  2. Defina o cabeçalho Access-Control-Allow-Origin correto no servidor que hospeda o script.
  3. [Opcional] Se você não tiver acesso ao servidor que hospeda o script, você pode considerar o uso de um proxy para retransmitir sua solicitação ao servidor e de volta ao cliente com os cabeçalhos corretos.

Uma vez que você possa acessar os detalhes do erro, você pode então definir para corrigir o problema, que provavelmente será com a biblioteca de terceiros ou com a rede.

Como identificar e prevenir erros no JavaScript

Embora os erros discutidos acima sejam os mais comuns e frequentes no JavaScript, você se deparará, confiar em alguns exemplos nunca pode ser suficiente. É vital entender como detectar e prevenir qualquer tipo de erro em um aplicativo JavaScript enquanto a desenvolve. Aqui está como você pode lidar com erros no JavaScript.

Erros de lançamento manual e catch

A maneira mais fundamental de lidar com erros que foram lançados manualmente ou pelo tempo de execução é pegá-los. Como a maioria das outras linguagens, o JavaScript oferece um conjunto de palavras-chave para lidar com os erros. É essencial conhecer a fundo cada uma delas antes de definí-las para lidar com os erros em seu aplicativo JavaScript.

throw

A primeira e mais básica palavra-chave do conjunto é throw. Como é evidente, a palavra-chave throw é usada para lançar erros para criar exceções no tempo de execução do JavaScript manualmente. Já discutimos isso antes na peça, e aqui está a essência do significado dessa palavra-chave:

  • Você pode throw qualquer coisa, incluindo números, strings, e objetos Error.
  • Entretanto, não é aconselhável lançar tipos de dados primitivos, como strings e números, já que eles não carregam informações de depuração sobre os erros.
  • Exemplo throw TypeError("Please provide a string")

try

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 notar que um bloco catch deve sempre seguir o bloco try para lidar com os erros de forma eficaz.

catch

A palavra-chave catch é usada para criar um bloco catch. Este bloco de código é responsável por lidar com os erros que o bloco try captura. Aqui está a sua sintaxe:

catch (exception) {
    // code to handle the exception here
}

E é assim que você implementa o try e os blocos catch juntos:

try {
    // business logic code
} catch (exception) {
    // error handling code
}

Ao contrário do C++ ou Java, você não pode anexar vários blocos catch a um bloco try em JavaScript. Isto significa que você não pode fazer isto:

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    }
} catch (exception) {
    if (exception instanceof RangeError) {
    // do something
    }
}

Ao invés disso, você pode usar uma declaração if...else ou uma declaração de caso de troca dentro do bloco catch único para lidar com todos os possíveis casos de erro. Isso seria parecido com isto:

try {
    // business logic code
} catch (exception) {
    if (exception instanceof TypeError) {
        // do something
    } else if (exception instanceof RangeError) {
        // do something else
    }
}

finally

A palavra-chave finally é usada para definir um bloco de código que é executado após um erro ter sido tratado. Este bloco é executado após o try e os blocos catch.

Além disso, o último bloco será executado independentemente do resultado dos outros dois blocos. Isto significa que mesmo que o bloco catch não possa lidar inteiramente com o erro ou um erro seja lançado no bloco catch, o intérprete executará o código no bloco final antes que o programa trave.

Para ser considerado válido, o bloco try no JavaScript precisa ser seguido ou por um catch ou por um bloqueio final. Sem nenhum deles, o intérprete irá levantar uma SyntaxError. Portanto, certifique-se de seguir seus blocos try com pelo menos um deles ao lidar com erros.

Gerencie os erros de maneira global 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 encontrar a imagem cuja URL é especificada, ela dispara seu método onerror para permitir ao usuário lidar com o erro.

Normalmente, você forneceria outra URL de imagem na chamada ao erro para que a tag img voltasse a cair. É 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 este recurso para criar um mecanismo global de gerenciamento de erros para o seu aplicativo. Aqui está como você pode fazer isso:

window.onerror = (event) => {
    console.log("Error occurred: " + event)
}

Com este manipulador de eventos, você pode se livrar dos múltiplos blocos try...catch espalhados em seu código e centralizar o gerenciamento de erros do seu aplicativo de forma similar ao gerenciamento de eventos. Você pode anexar vários manipuladores de erros à janela para manter o Princípio de Responsabilidade Única dos princípios de design SOLID. O intérprete irá percorrer todos os manipuladores até que chegue ao apropriado.

Transmita os erros via callbacks

Enquanto funções simples e lineares permitem que o gerenciamento de erros permaneça simples, os callbacks podem complicar o caso.

Considere o seguinte trecho de código:

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 operações e retorna o resultado mais tarde com a ajuda de um callbacks.

Se você tentar inserir uma string ao invés de 4 na chamada de função, você receberá NaN como resultado.

Isto precisa ser tratado adequadamente. Aqui está 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) }

Isto deve resolver o problema de forma ideal. Entretanto, se você tentar passar uma string para a chamada de função, você receberá isto:

Exemplo de erro com o argumento errado.
Exemplo de erro com o argumento errado.

Mesmo que você tenha implementado um bloco try enquanto chamava a função, ele ainda diz que o erro não foi corrigido. O erro é lançado após o bloco catch ter sido executado devido ao timeout delay.

Isso pode ocorrer rapidamente em chamadas de rede, onde atrasos inesperados se infiltram. Você precisa cobrir tais casos enquanto desenvolve seu aplicativo.

Aqui está como você pode lidar corretamente com os erros nos callbacks:

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, o resultado no console será:

TypeError exemplo com argumento ilegal.
TypeError exemplo com argumento ilegal.

Isto indica que o erro foi tratado de forma apropriada.

Resolva os erros Promises

A maioria das pessoas tende a preferir promises para lidar com atividades assíncronas. As promises têm outra vantagem – uma promises rejeitada não termina o seu script. No entanto, você ainda precisa implementar um bloqueio para lidar com os erros nas promises. Para entender isso melhor, vamos reescrever a função calculateCube() usando promise:

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 timeout do código anterior foi isolado na função delay para compreensão. Se você tentar inserir uma string ao invés de 4, a resultado que você obtém será semelhante a esta:

TypeError exemplo com um argumento ilegal em Promise
TypeError exemplo com um argumento ilegal em Promise

Mais uma vez, isto se deve ao lançamento do erro em Promise depois de tudo o resto ter completado a execução. A solução para este problema é simples. Simplesmente adicione uma chamada catch() à cadeia de promises como esta:

calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))

Agora o resultado será:

Exemplo do TypeError com argumento ilegal.
Exemplo do TypeError com argumento ilegal.

Você pode observar como é fácil lidar com erros com promises. Além disso, você pode encadear um bloco finally() e a chamada de promessa para adicionar código que será executado após o gerenciamento de erros ter sido completado.

Alternativamente, você também pode lidar com erros em promises usando a técnica tradicional try-catch-finally. Aqui está como seria a sua promessa nesse caso:

try {
    let result = await calculateCube("hey")
    console.log(result)
} catch (e) {
    console.log(e)
} finally {
    console.log('Finally executed")
}

Entretanto, isto funciona apenas dentro de uma função assíncrona. Portanto, a maneira mais preferida de lidar com erros em promises é encadear catch e finally para a chamada de promessa.

throw/catch vs onerror() vs Callbacks vs Promises: Qual é a melhor?

Com quatro métodos à sua disposição, você deve saber como escolher o mais apropriado em qualquer caso de uso. Aqui está como vocês podem decidir por si mesmos:

throw/catch

Você estará usando este método a maior parte do tempo. Certifique-se de implementar condições para todos os possíveis erros dentro do seu bloco catch e lembre-se de incluir um bloco final se você precisar executar algumas rotinas de limpeza de memória após o bloco try.

No entanto, muitos blocos de throw/catch podem tornar seu código difícil de ser mantido. Se você se encontrar em tal situação, você pode querer lidar com erros através do manipulador global ou do método de promessa.

Ao decidir entre try/catch blocks assíncronos e promise catch(), é aconselhável optar por try/catch blocks assíncronos porque eles tornarão seu código simples e fácil de depurar.

onerror()

É melhor usar o método onerror() quando você sabe que seu aplicativo tem que lidar com muitos erros, e eles podem estar bem espalhados por toda a base de código. O método onerror permite que você trate os erros como se eles fossem apenas mais um evento tratado 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 se estabelecer em projetos menores com um escopo de erro menor. Se você tem certeza de que seu aplicativo não vai arremessar muitos erros, o método tradicional de arremesso/catch funcionará melhor para você.

Callbacks e Promises

O gerenciamento de erros nos callbacks e promises difere devido ao seu design e estrutura de código. Entretanto, se você escolher entre estes dois antes de ter escrito seu código, seria melhor optar por promises.

Isto porque os promises têm uma construção embutida para encadear um catch() e um bloco finally() para lidar facilmente com os erros. Este método é mais fácil e limpo do que definir argumentos adicionais/reusar argumentos existentes para lidar com os erros.

Mantenha o controle das mudanças com os repositórios Git

Muitos erros frequentemente surgem devido a erros manuais na base de código. Ao desenvolver ou depurar o seu código, você pode acabar fazendo mudanças desnecessárias que podem causar novos erros na sua base de código. O teste automatizado é uma ótima maneira de manter seu código sob controle após cada mudança. Entretanto, ele só pode dizer a você se algo estiver errado. Se você não fizer backups frequentes do seu código, você vai acabar perdendo tempo tentando corrigir uma função ou um script que estava funcionando muito bem antes.

É aqui que o git desempenha seu papel. Com uma estratégia de comprometimento apropriada, você pode usar seu histórico de git como um sistema de backup para visualizar seu código à medida que ele evolui através do desenvolvimento. Você pode facilmente navegar por seus commits mais antigos e descobrir a versão da função funcionando bem antes, mas lançando erros após uma mudança não relacionada.

Você pode então restaurar o código antigo ou comparar as duas versões para determinar o que deu errado. Ferramentas modernas de desenvolvimento web como GitHub Desktop ou GitKraken ajudam você a visualizar estas mudanças lado a lado e descobrir os erros rapidamente.

Um hábito que pode ajudá-lo a cometer menos erros é executar revisões de código sempre que você faz uma mudança significativa no seu código. Se você estiver trabalhando em equipe, você pode criar uma solicitação de puxar e pedir a um membro da equipe que a revise minuciosamente. Isto o ajudará a usar um segundo par de olhos para detectar quaisquer erros que possam ter passado por você.

Melhores práticas para o gerenciamento de erros no Javascript

Os métodos acima mencionados são adequados para ajudá-lo a projetar uma abordagem robusta de gerenciamento de erros para o seu próximo aplicativo JavaScript. Entretanto, seria melhor manter algumas coisas em mente enquanto as implementa para obter o melhor de sua prova de erros. Aqui estão algumas dicas para ajudar você.

1. Use erros personalizados ao lidar com exceções operacionais

Introduzimos erros personalizados no início deste guia para lhe dar uma ideia de como personalizar o gerenciamento de erros para o caso único do seu aplicativo. É aconselhável usar os erros personalizados sempre que possível ao invés da classe genérica Error, pois ela fornece mais informações contextuais ao ambiente de chamada sobre o erro.

Além disso, os erros personalizados permitem que você modere a forma como um erro é exibido no ambiente de chamada. Isto significa que você pode escolher ocultar detalhes específicos ou exibir informações adicionais sobre o erro como e quando você desejar.

Você pode ir ao ponto de formatar o conteúdo de erro de acordo com suas necessidades. Isto lhe dá melhor controle sobre como o erro é interpretado e tratado.

2. Não aceite nenhuma exceção

Mesmo os desenvolvedores mais experientes geralmente cometem um erro de novato – consumindo níveis de exceção no fundo do seu código.

Você pode se deparar com situações em que você tem um parte de código que é opcional para rodar. Se ele funcionar, ótimo; se não funcionar, você não precisa fazer nada a respeito.

Nesses casos, é frequentemente tentador colocar esse código em um bloco try e anexar um bloco catch vazio a ele. No entanto, ao fazer isso, você deixará esse pedaço de código aberto para causar qualquer tipo de erro e se safar com ele. Isto pode se tornar perigoso se você tiver uma grande base de código e muitos exemplos de construções de gerenciamento de erros tão ruins.

A melhor maneira de lidar com exceções é determinar um nível no qual todas elas serão tratadas e elevá-las até lá. Este nível pode ser um controlador (em um aplicativo de arquitetura MVC) ou um middleware (em um aplicativo tradicional orientado ao servidor).

Desta forma, você vai saber onde você pode encontrar todos os erros que ocorrem em seu aplicativo e escolher como resolvê-los, mesmo que isso signifique não fazer nada a respeito deles.

3. Use uma estratégia centralizada para registros e alertas de erro

O registro de um erro é frequentemente uma parte integrante do seu gerenciamento. Aqueles que não desenvolvem uma estratégia centralizada para o registro de erros podem perder informações valiosas sobre o uso do seu aplicativo.

Os registros de eventos de um aplicativo podem ajudar você a descobrir dados cruciais sobre erros e ajudar a depurá-los rapidamente. Se você tiver mecanismos de alerta apropriados configurados em seu aplicativo, você pode saber quando um erro ocorre em seu aplicativo antes que ele chegue a uma grande seção de sua base de usuários.

É aconselhável usar um registrador pré-construído ou criar um para atender às suas necessidades. Você pode configurar este registrador para lidar com erros com base em seus níveis (aviso, depuração, informação, etc.), e alguns registradores chegam ao ponto de enviar os registros para servidores de registro remoto imediatamente. Desta forma, você pode observar como a lógica do seu aplicativo se comporta com usuários ativos.

4. Notifique os usuários sobre erros de forma apropriada

Outro bom ponto a ter em mente ao definir sua estratégia de gerenciamento de erros é manter o usuário em mente.

Todos os erros que interferem no funcionamento normal do seu aplicativo devem apresentar um alerta visível ao usuário para notificá-lo de que algo deu errado para que o usuário possa tentar encontrar uma solução. Se você conhece uma correção rápida para o erro, como tentar novamente uma operação ou sair e voltar a entrar, certifique-se de mencioná-lo no alerta para ajudar a corrigir a experiência do usuário em tempo real.

No caso de erros que não causam nenhuma interferência na experiência diária do usuário, você pode considerar suprimir o alerta e registrar o erro em um servidor remoto para resolução posterior.

5. Implemente um Middleware (Node.js)

O ambiente Node.js suporta middlewares para adicionar funcionalidades para aplicativos do servidor. Você pode usar este recurso para criar um middleware de manipulação de erros para o seu servidor.

O benefício mais significativo do uso de middleware é que todos os seus erros são tratados de forma centralizada em um só lugar. Você pode optar por ativar/desativar esta configuração para fins de teste facilmente.

Aqui está como você pode criar um middleware básico:

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 então usar este middleware em seu aplicativo desta forma:

const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')

app.use(errorLoggerMiddleware)

app.use(returnErrorMiddleware)

Agora você pode definir uma lógica personalizada dentro do middleware para tratar os erros de forma apropriada. Você não precisa mais se preocupar em implementar construções individuais de manipulação de erros em toda a sua base de código.

6. Reinicie o seu aplicativo para lidar com erros de programador (Node.js)

Quando os aplicativos Node.js encontram erros de programador, eles podem não necessariamente lançar uma exceção e tentar fechar o aplicativo. Tais erros podem incluir problemas decorrentes de erros de programador, como alto consumo de CPU, inchaço da memória ou vazamentos de memória. A melhor maneira de lidar com isso é reiniciar graciosamente o aplicativo, travando-o através do modo de cluster Node.js ou de uma ferramenta única como PM2. Isso pode garantir que o aplicativo não trave na ação do usuário, apresentando uma experiência de usuário terrível.

7. Catch All Uncaught Exceptions (Node.js)

Você nunca pode ter certeza de ter coberto todos os erros possíveis que podem ocorrer em seu aplicativo. Portanto, é essencial implementar uma estratégia de recuperação para pegar todas as exceções não detectadas em seu aplicativo.

Aqui está 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. Obtenha todas as rejeições de promeses não gerenciadas (Node.js)

Semelhante a como você nunca pode cobrir todas as possíveis exceções, há uma grande chance de que você possa perder de lidar com todas as possíveis rejeições de promises. No entanto, ao contrário das exceções, as rejeições de promises não lançam erros.

Então, uma promessa importante que foi rejeitada pode passar como um aviso e deixar seu aplicativo aberto para a possibilidade de se deparar com um comportamento inesperado. Por isso, é crucial implementar um mecanismo de recurso para lidar com a rejeição de promises.

Aqui está 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 no JavaScript. Em alguns casos, você pode até mesmo precisar lançar os erros intencionalmente para indicar a resposta correta aos seus usuários. Portanto, compreender sua anatomia e tipos é muito crucial.

Além disso, você precisa estar equipado com as ferramentas e técnicas corretas para identificar e prevenir erros que possam derrubar 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 JavaScript que você ainda não foi capaz de resolver? Alguma técnica para lidar com erros JS de forma construtiva? Nos deixe sabendo nos comentários abaixo!

Kumar Harsh

Kumar is a software developer and a technical author based in India. He specializes in JavaScript and DevOps. You can learn more about his work on his website.