Lidando com Erros e a Pilha de Chamadas de Funções em JavaScript
Quando um erro ocorre dentro de uma função e não é tratado, o interpretador de JavaScript sai dessa função e busca um tratador de erros no código externo. Veja o exemplo a seguir:
function A() {
console.log("func A starts");
callSomeFunc();
console.log("func A ends");
}
A();
console.log("program ends");Neste caso, a função A chama a função não definida callSomeFunc(). Assim, quando a função A é chamada, a execução é interrompida e o interpretador busca um tratador de erros no código externo. Como não há uma construção try..catch ao redor da chamada da função A, a execução do programa termina abruptamente. A saída no console será:
func A starts
Uncaught ReferenceError: callSomeFunc is not defined
at A (index.html:11:2)
at index.html:30:6O mesmo se aplica a chamadas de funções aninhadas. Se um erro ocorre em uma função interna e não é tratado, o interpretador também sai para o contexto externo, a função externa. Se o erro também não for tratado na função externa, o interpretador continua buscando até encontrar um tratador de erros. Se não encontrar nenhum tratador de erro em nenhuma função nem no código global, o programa termina. Por exemplo:
function A() {
console.log("func A starts");
callSomeFunc();
console.log("func A ends");
}
function B() {
console.log("func B starts");
A();
console.log("func B ends");
}
function C() {
console.log("func C starts");
B();
console.log("func C ends");
}
C();
console.log("program ends");Aqui, a função C chama a função B, que por sua vez chama a função A, e esta tenta chamar a inexistente callSomeFunc. Como resultado, ocorre um erro na função A. Como a função A não tratou o erro, o interpretador busca um tratador na função B e depois na função C, e finalmente no contexto global. Mas, como em nenhum lugar o erro é tratado, a execução do programa é finalizada após o erro ocorrer:
func C starts
func B starts
func A starts
Uncaught ReferenceError: callSomeFunc is not defined
at A (index.html:11:2)
at B (index.html:16:2)
at C (index.html:27:2)
at index.html:31:1Agora, vamos definir um tratador de erros em uma das funções, por exemplo, na função C:
function A() {
console.log("func A starts");
callSomeFunc();
console.log("func A ends");
}
function B() {
console.log("func B starts");
A();
console.log("func B ends");
}
function C() {
console.log("func C starts");
try {
B();
} catch {
console.log("Error occured");
}
console.log("func C ends");
}
C();
console.log("program ends");Com a introdução do tratador de erros, o interpretador primeiro busca um tratador na função A. Como o erro não é tratado lá, ele verifica no código envolvente na função B, e não encontrando, segue para a função C onde o erro é tratado, permitindo que o programa continue sua execução:
func C starts func B starts func A starts Error occured func C ends program ends
Dessa forma, as funções A e B, que não trataram o erro, não são mais executadas após o erro ocorrer.
Propagando Erros pela Pilha de Chamadas de Funções
Às vezes, erros são tratados em algum lugar dentro das chamadas aninhadas de outras funções. Vamos considerar a seguinte situação:
// classe de um banco de dados fictício
class Database {
constructor() {
this.data = ["Tom", "Sam", "Bob"];
}
// método para obter dados
getItem(index) {
if (index >= 0 && index < this.data.length) return this.data[index];
// se o índice for inválido, lançamos um erro
else throw new RangeError("Invalid index");
}
// método para abrir o banco de dados
open() {
console.log("Database has opened");
}
// método para fechar o banco de dados
close() {
console.log("Database has closed");
}
}
// função para obter um objeto do banco de dados pelo índice
function get(index) {
const db = new Database();
db.open(); // simulamos a abertura do banco de dados
try {
return db.getItem(index); // retornamos o elemento obtido
} catch (err) {
console.error(err); // se ocorrer um erro, tratamos aqui
throw err; // lançamos o erro novamente para ser tratado mais acima na pilha
} finally {
db.close(); // simulamos o fechamento do banco de dados
}
}
// função para exibir o resultado
function printResult() {
try {
const item = get(5); // tentamos obter o elemento com índice 5
console.log("Got from database:", item); // exibimos o elemento obtido
} catch (err) {
console.error("Error during database retrieval:", err); // tratamos o erro aqui
}
}
printResult();Este exemplo define uma classe Database simulada para interagir com um banco de dados contendo os nomes "Tom", "Sam" e "Bob". As funções open e close são utilizadas para simular a abertura e o fechamento do banco de dados, respectivamente. O método getItem retorna um elemento baseado no índice fornecido, mas lança um erro do tipo RangeError caso o índice seja inválido.
A função get é uma camada adicional que usa a classe Database para obter um item por índice, tratando os erros localmente e lançando-os novamente quando necessário. Isso permite que a função que realiza a chamada, printResult, seja informada sobre o erro, tratando-o apropriadamente.
O uso do bloco finally garante que o banco de dados seja fechado independentemente de erros ocorrerem, promovendo uma boa prática de gerenciamento de recursos. Essa abordagem é comum em aplicações que interagem com bases de dados, onde é crucial garantir que as conexões sejam fechadas após o uso para evitar vazamentos de recursos.
A saída do console será:
Database has opened Error during database retrieval: RangeError: Invalid index at Database.getItem Database has closed Error during database retrieval: RangeError: Invalid index