View on GitHub

Capacitacao-CEOS-2-Javascript

Try, Catch, Finally, Throw

Erros acontecem, e todo mundo sabe disso. Quem nunca estava programando e se deparou com um “variavel is not defined”, ou então com um “Array index of bounds”. Esse então me assombra até hoje: “Segmentation error”.

Mas não é por que erros acontecem que nós desistimos de programar não é mesmo? Nós encontramos meios de consertá-los ou, quando não é possível, contorná-los. Porém, muitas vezes, erros ocorrem que não dependem exclusivamente da gente, ou então, não sabemos ao certo que erro vai acontecer e não conseguimos prever seu acontecimento. E então? Podemos desistir agora?

De jeito nenhum! Pra tudo tem jeito, menos pra morte, então, nesse tópico, iremos discutir a maneira que o JavaScript tem para lidar com erros.

Basicamente toda (ou a maioria das) execução de um código no qual pode ocorrer um erro segue o seguinte cronograma:

Logicamente você viu 4 palavras chave em negrito quando falamos de tratamento de erros, sendo elas tento, jogo, capturo e finalmente. Vamos nos atentar ao modo que JavaScript trata um erro, e, para isso, vamos usar a forma em inglês dessas palavras, ou seja: try, throw, catch, finally.


Try

Como o próprio nome diz, o try tenta executar um código e ele perceberá quando um erro acontecer.


Catch

Antes de vermos como funciona o try, vamos ao seu grande companheiro, o catch. Eles são inseparáveis e sempre andam juntos, já que os erros que o try detectar, alguem tem que capturar, e quem faz isso é o catch.

Agora podemos ver um exemplo bem simples. Vamos pensar em uma função meio, que recebe um array de objetos e chama o método meExecute do objeto que está no meio do array, ou seja:

	function meio(arr){
		arr[(arr.length-1)/2].meExecute()
	}

	function metodoCerto(){
		console.log("Fui executado!");
	}

	function metodoErrado(){
		console.log("Não deveria ser executado!");
	}

	let a = [
		{meExecute: metodoErrado},
		{meExecute: metodoCerto},
		{meExecute: metodoErrado}
	]

	meio(a)
	// Imprimirá "Fui executado"


Tudo tranquilo e tudo certo não é mesmo? Essa função funciona perfeitamente… mas só para arrays de tamanho ímpar, pois eles possuem um elemento do meio bem definido. Tente executar o código, agora com o nosso array a da seguinte forma:

	let a = [
		{meExecute: metodoErrado},
		{meExecute: metodoCerto}
	]
	meio(a)


E o que acontece no terminal? Se você viu algo parecido com isso:

/home/batista/Projetos/CEOS/Tutoring/testesRef.js:2
        arr[(arr.length-1)/2].meExecute()
                              ^

TypeError: Cannot read property 'meExecute' of undefined
    at meio (/home/batista/Projetos/CEOS/Tutoring/testesRef.js:2:24)
	...


Estamos no caminho certo, ocorreu um erro! Existem dois modos de contornar esse problema, sendo eles:

O primeiro modo vocês sabem como se faz, é trivial:

function meio(arr){
	if(arr.length % 2 === 0){
		console.log("Impossível chamar essa função em um array de tamanho par!");
		return;
	}
	arr[(arr.length-1)/2].meExecute()
}


E isso funciona muito bem para esse caso. Mas vamos usar agora nossos grandes amigos try e catch.

Para fazer com que o try tente executar um pedaço de código, basta envolvê-lo com chaves e usar o try antes:

function meio(arr){
	try{
		arr[(arr.length-1)/2].meExecute();
	}
}


Se tentarmos executar esse código do jeito que está, não conseguiremos, isso por que o try nunca pode vir desacompanhado, vamos ver como utilizar um dos seus companheiros, o catch.

O catch captura erros e então pode fazer operações com o erro que ele espera. Logo, precisamos:

Podemos fazer isso com a sintaxe:

	
	try{
		//Gerando meu erro aqui!
	}
	catch(meuErroAqui){
		//Tratando meu erro aqui!
	}


Bem simples não é mesmo? Podemos então fazer nossa função do seguinte modo:

function meio(arr){
	try{
		arr[(arr.length-1)/2].meExecute();
	}
	catch(e){
		console.log("Não foi possível executar o método!");
	}
}


E pronto, temos a nossa função que trata o erro de uma ótima maneira agora!

Ah, mas isso é muito complicado, qual a vantagem de usar try catch ao invés de só prever o erro?

Primeiramente, a vantagem é que nós não precisamos prever especificamente o erro. Prevemos qualquer tipo de erro e então o tratamos. Quer um exemplo?

Pegue a mesma função que nós temos. Ela tentará executar o método meExecute do objeto do meio, e já adaptamos ela na nossa solução anterior sem try catch para prever esse erro. Mas o que acontece quando o objeto não possuir essa função meExecute? Isso mesmo. Um erro.

Tente visualizar isso. Execute o código abaixo:

function meio(arr){
	if(arr.length % 2 === 0){
		console.log("Impossível chamar essa função em um array de tamanho par!");
		return;
	}
	arr[(arr.length-1)/2].meExecute()
}

function metodo(){
	console.log("Fui executado!");
}

let a = [
	{meExecute: metodo},
	{/*nada aqui*/},
	{meExecute: metodo}
]

meio(a)


Ao executar, seu terminal ficará mais ou menos assim:

/home/batista/Projetos/CEOS/Tutoring/testesRef.js:6
        arr[(arr.length-1)/2].meExecute()
                              ^

TypeError: arr[((arr.length - 1) / 2)].meExecute is not a function
    at meio (/home/batista/Projetos/CEOS/Tutoring/testesRef.js:6:24)
    at Object.<anonymous> (/home/batista/Projetos/CEOS/Tutoring/testesRef.js:19:1)


Ou seja, outro erro ocorreu e não foi tratado! Agora tente executar esse mesmo problema, mas agora com a versão com try catch:

function meio(arr){
	try{
		arr[(arr.length-1)/2].meExecute()
	}
	catch(e){
		console.log("Não foi possível executar o método!")
	}
}

function metodo(){
	console.log("Fui executado!");
}

let a = [
	{meExecute: metodo},
	{/*nada aqui*/},
	{meExecute: metodo}
]

meio(a)


E como podemos ver, o erro foi detectado, e tratado! Podemos ainda executar mais coisas após essa nossa função:

let a = [
	{meExecute: metodo},
	{/*nada aqui*/},
	{meExecute: metodo}
]

meio(a);

console.log("Eu continuo a executar o código");
console.log("Mesmo após o erro!");
console.log("Pois ele foi tratado!");
console.log("Viva viva!");


Já isso não acontece quando não tratamos o erro.

Já deu pra perceber a vantagem clara do try catch não é mesmo? Podemos manipular inúmeros erros com uma só estrutura!

Finally

E agora, podemos seguir com o outro amigo inseparável do try, o finally.

Como seu próprio nome diz, o finally executa um código após o try terminar de tentar executar outro pedaço de código, porém, o finally sempre vai executar esse código, independente de um erro ter acontecido ou não.

Podemos ver isso manipulando um pouco a função que tinhamos antes para que agora tenhamos um finally, sua sintaxe é bem parecida com a do try:

function meio(arr){
	try{
		arr[(arr.length-1)/2].meExecute()
	}
	catch(e){
		console.log("Não foi possível executar o método!")
	}
	finally{
		console.log("Obrigado por chamar a função meio.");
		console.log("Tenha um bom dia");
	}
}


Agora tente executar esse código, e tente também provocar um erro dentro do try. Você vai perceber que as frases “Obrigado por chamar a função meio. Tenha um bom dia” sempre são imprimidas na tela, independente de um erro ter acontecido.

Vale lembrar que um finally não precisa de um catch, ou vice versa, mas ambos precisam do try.

Throw

Vimos que o nosso try pode detectar erros que o JavaScript também consegue detectar. Mas aogra, imagina que ocorra um erro no contexto da nossa aplicação, mas que para o JavaScript esteja tudo bem. Como isso poderia acontecer?

Vamos adaptar a nossa função meio. Ela é mais simples agora, apenas recebe um array simples e imprime o valor do elemento do meio:

	function meio(arr){
		console.log(arr[(arr.length-1)/2]);
	}

	let a = [1,2,3];

	meio(a);
	// Imprimirá 2


Bem simples não é mesmo? Mas agora, vamos imaginar que esquecemos de passar o a como parâmetro, ou seja:

	let a = [1,2,3];

	meio();


Teremos um erro no terminal:

 console.log(arr[(arr.length-1)/2]);
                             ^

TypeError: Cannot read property 'length' of undefined
    at meio (/home/batista/Projetos/CEOS/Tutoring/testesRef.js:2:23)


Como podemos consertar isso? Exatamente, try catch:

function meio(arr){
	try{
		console.log(arr[(arr.length-1)/2]);
	}
	catch(e){
		console.log("Um erro ocorreu na função!");
	}
	
}

let a = [1,2,3];

meio();
// Imprimirá um erro ocorreu na função


Pronto, e viveram felizes para sempre!

Ou será que não? Vamos tentar executar essa função passando agora um array de tamanho par:

let a = [1,2];

meio(a);


E o que temos no terminal? “Um erro ocorreu!” certo? ERRADO! Temos um belo:

	undefined


Mas por que isso aconteceu? Para o JavaScript, nenhum erro aconteceu, é muito simples. Um elemento que não está no array é indefinido, ou seja undefined. Pode seguir a diante.

Mas para nós, isso não faz sentido nenhum! Isso é um erro e precisa ter tratado! Como podemos fazer isso?

Para isso, nós jogamos um erro personalizado com o throw.

O throw joga um objeto do tipo erro no nosso try para o nosso catch.

Sua sintaxe pode ser vista no exemplo abaixo:

function meio(arr){
	try{
		if(arr.length % 2 === 0)
			throw(new Error);
			
		console.log(arr[(arr.length-1)/2]);
	}
	catch(e){
		console.log("Um erro ocorreu na função!");
	}
	
}


Ou seja, jogue um novo Erro. E então esse erro será capturado pelo nosso catch.

Isso é muito útil quando queremos que nossa aplicação dispare erros para serem tratados mesmo quando esses passam despercebidos pelo JavaScript.


Objeto tipo Error

Se você ficou com dúvidas quando falei:

“Dar o erro que iremos manipular ao catch”

ou então

“new Error”

ou até mesmo

“jogue um novo erro”

Não se preocupe, tudo vai fazer sentido agora. Sabe, um Erro em JavaScript não é um bicho de sete cabeças, na verdade, ele é algo muito simples, e você já trabalhou com ele antes. O Erro nada mais é do que um Objeto.

O instanciamos com a palavra chave new (será explicado melhor no tópico 16 - Classes) e o passamos para o catch com algum nome, no nosso caso, usamos apenas a letra e.

Um objeto de erro possui 2 propriedades:

Podemos ver essas propriedades em açao na nossa versão modificada da função meio:

function meio(arr){
	try{
		if(arr.length % 2 === 0)
			throw(new Error);

		console.log(arr[(arr.length-1)/2]);
	}
	catch(e){
		console.log(`Nome: ${e.name}`);
		console.log(`Mensagem: ${e.message}`);
	}
	
}


Porém, se executarmos o nosso código usando:

	let a = [1,2];
	meio(a);


Veremos que o nome do erro é apenas “Error” e a mensagem do erro é undefined. Por que isso aconteceu?

Isso aconteceu pois no nosso construtor do objeto Error não passamos a mensagem como parâmetro da construção, e o nome é “Error” por padrão.

Podemos especificar que erro estamos enfrentando modificando a nossa função:

function meio(arr){
	try{
		if(arr.length % 2 === 0)
			throw(new Error("Tamanho do array par!"));

		console.log(arr[(arr.length-1)/2]);
	}
	catch(e){
		console.log(`Nome: ${e.name}`);
		console.log(`Mensagem: ${e.message}`);
	}
	
}


E assim podemos acessar o campo de mensagem de maneira apropriada.

Mas e o nome do Erro?

Como isso mexe com várias coisas que ainda não vimos nesse módulo, iremos deixar essa parte um pouco de lado, mas para aqueles que quiserem se aventurar no mundo dos tipos de Erro, vale muito a pena a leitura do link abaixo:

Objeto Error - MDN
Tempo médio de leitura: 5 minutos


E aí está. Agora você está preparado para lidar com erros em JavaScript, tanto seus, quanto da linguagem, quanto de outras pessoas!