Programação Assíncrona - JavaScript

Na execução padrão do JavaScript, as instruções são executadas sequencialmente, uma após a outra. Ou seja, primeiro a primeira instrução é executada, depois a segunda e assim por diante. No entanto, o que acontece se uma dessas operações levar um tempo considerável? Por exemplo, ao realizar um trabalho de alta carga, como uma solicitação de rede ou acesso a um banco de dados, o que pode levar um tempo indefinido e, às vezes, prolongado. Como resultado, na execução sequencial, todas as operações subsequentes esperarão pela conclusão dessa operação. Para evitar essa situação, o JavaScript permite o uso de funções assíncronas.

Por exemplo, vamos definir uma simples função assíncrona que emula um trabalho demorado usando a chamada setTimeout() com um atraso de 1 segundo e, em seguida, imprime um número aleatório no console:

function asyncFunction() {
  setTimeout(()=> {
      let result = 22;
      console.log("result:", result);
  }, 1000);
}
asyncFunction();
console.log("Fim do programa");

Em vez de setTimeout(), poderia haver uma solicitação a um banco de dados ou um recurso de rede, o que poderia levar um tempo considerável e cujo resultado seria obtido após algum tempo. No final, o valor do número seria exibido no console no final da execução do programa:

Fim do programa
result: 22

Aqui vemos que a função assíncrona não bloqueia a execução das demais instruções do programa. No entanto, ao trabalhar com essas funções, podemos enfrentar alguns problemas. Por exemplo, funções assíncronas não retornam o resultado do cálculo assíncrono usando a palavra-chave return, mas o passam como um parâmetro para a função de callback.

function asyncFunction() {
  let result;
  setTimeout(()=>{result = 22;}, 1000);
  return result;
}
const asyncResult = asyncFunction();
console.log("result:", asyncResult) // result: undefined

Aqui a função assíncrona asyncFunction é chamada de maneira síncrona e, como resultado, obtemos um resultado indefinido. Isso ocorre porque a variável asyncResult é definida antes que a função asyncFunction gere o resultado.

Outro problema está relacionado ao lançamento de exceções com o operador throw:

function asyncFunction() {
  let result;
  setTimeout(()=>{
      result = 22;
      if(result < 50) { 
          throw new Error("Valor incorreto");      
      } 
  }, 1000);
  return result;
}
try {  
  const asyncResult = asyncFunction();
  console.log("result:", asyncResult)
} 
catch(error) {  
  console.error("Error:", error); // Esta linha NÃO será executada
}
console.log("Fim do programa");

Aqui, a manipulação de erros no bloco catch não funcionará, pois no momento em que o erro for lançado, o código que chamou a função já terá avançado.

Inicialmente, o tratamento de resultados e erros em funções assíncronas era feito utilizando funções de callback, que eram passadas para outra função e chamadas posteriormente em algum momento. Um padrão simples de uso de callbacks:

function asyncFunction(callback) {
  console.log("Antes da chamada do callback");  
  callback();  
  console.log("Depois da chamada do callback");
}
function callbackFunc() {  
  console.log("Chamada do callback");
}
asyncFunction(callbackFunc);

Aqui, a função asyncFunction (condicionalmente assíncrona) recebe uma função de callback e a chama no código.

Por exemplo, usamos um callback para obter e processar o resultado e o erro de uma função assíncrona:

function handleResult(error, result) {    
  if(error) {     // se foi passado um erro
      console.error(error);   
  }  
  else {     // se a função assíncrona foi bem-sucedida
      console.log("Result:", result);    
  }  
}

function asyncFunction(callback) {
  setTimeout(()=> {
      let result = Math.floor(Math.random() * 100) + 1;
      if(result < 50) { 
          // se for menor que 50, definimos um erro
          callback(new Error("Valor incorreto: " + result), null);      
      } 
      else {
          // nos outros casos, definimos o resultado
          callback(null, result);
      }
  }, 1000);
}
asyncFunction(handleResult);

A função handleResult é passada como um callback para a função asyncFunction:

asyncFunction(handleResult);

Utilizamos o método Math.random() para gerar um número aleatório entre 1 e 100.

let result = Math.floor(Math.random() * 100) + 1;

Se o número for menor que 50, um erro é gerado, caso contrário, o número é retornado como resultado.

if(result < 50) { 
  // se for menor que 50, definimos um erro
  callback(new Error("Valor incorreto: " + result), null);      
} 

Nos outros casos, definimos o resultado e passamos null para o erro:

else { // nos outros casos, definimos o resultado callback(null, result); }

Exemplo da saída no console para o processamento bem-sucedido (quando o número gerado é igual ou maior que 50):

Result: 70

Se o número gerado for menor que 50, será exibido um erro:

Error: Error: Valor incorreto: 22

Esta é a forma clássica de usar callbacks para processar o resultado de uma função assíncrona. No entanto, ela tem pelo menos uma grande desvantagem: o uso excessivo de funções de callback pode levar à criação de uma estrutura de código conhecida entre os desenvolvedores JavaScript como "callback hell" (inferno dos callbacks). Essa estrutura de código surge quando um callback em uma função assíncrona chama outra função assíncrona, cujo callback, por sua vez, pode chamar uma terceira função assíncrona e assim por diante. Um exemplo dessa estrutura:

asyncFunction((error, result) => {    
  asyncFunction2((error2, result2) => { 
      asyncFunction3((error3, result3) => {
          asyncFunction4((error4, result4) => {              
              // algum código
          });          
      });      
  });  
});

Para resolver esse problema, a partir do padrão ES2015, o JavaScript adicionou suporte a Promises, que serão discutidas no próximo tema.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com