JQuery - Utilizando Ajax - Entendendo o modo Assíncrono e Síncrono


As palavras síncrono e assíncrono podem soar estranhas quando aplicados ao mundo da TI. Serão mais dois dentre os tantos termos inventados por 'gurus' da web para vender livros?

É bem provável que, sim, que a adoção destes termos tenha sido feita por escritores ou editoras da área porém acredito que estas palavras são bem interessantes para descrever a real funcionalidade de tais sistemas. Utilizamos muito estes conceitos no javascript (como você já deve saber), mais especificamente na utilização do chamado Ajax (outro termo de gurus ?) .

Vamos tratar do assunto utilizando o serviço do Google Maps como exemplo, mais especificamente o serviço de busca de endereços.

Este serviço pode ser acessado utilizando-se a seguinte URL:
http://maps.googleapis.com/maps/api/geocode/json?address=endereco&sensor=false

Além disto vamos utilizar jQuery para fazer as requisições ajax e manitulação do DOM

Recomento fortemente a utilização Google Chrome (Firefox também pode ser utilizado sem problemas) mas isto acredito que isto você já faz pois não é possível que quem desenvolve para Web ainda utilize o IE :-)

Vamos ver na prática qual a diferença entre uma requisição assíncrona e uma síncrona e as maneiras como podemos tratar as informações retornadas.

Em primeiro lugar vamos ver como uma requisição assíncrona funciona:


<html>
  <head>
 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
  </head>
  <body>
 <div id="endereco">
</div>
</body>
  <script type="text/javascript">

 /**======= Busca  =========*/
 var busca =
 {
  // Propriedade de busca
  info : '',

  // Chama API Google Maps
  googleapi: function(endereco)  {
 
   $.ajax({
    'url'   : 'http://maps.googleapis.com/maps/api/geocode/json?address='+encodeURIComponent(endereco)+'&sensor=false',
    'async'   : true,
    'success' : function(data)
    {
   
     busca.info = data.results[0].formatted_address;
     $('#endereco').html(busca.info);
   
    }
   });
  }
 };

 /**======= Evento  =========*/
 $(document).ready(function(){
  busca.googleapi('Av. Paulista 100, São Paulo');
 });
    </script>
</html>


O script acima vai fazer a consulta por Ajax na API do Google através do método 'busca.googleapi()' e retornar o endereço completo de 'Av Paulista 100, São Paulo'. Retornando o valor em 'busca.info' , utilizamos este para injetar no DOM do documento através de '$('#endereco').html(busca.info)'.

O script vai funcionar muito bem pois '$('#endereco').html(busca.info)' está dentro de 'success', que é iniciado quando o Ajax consegue retornar os valores da consulta, após alguns micro segundos.

Mas o que aconteceria se, por exemplo, mandássemos '$('#endereco').html(busca.info)' fora de 'success', ou mesmo fora do método 'busca.googleapi()' ?


<html>
  <head>
 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
  </head>
  <body>
 <div id="endereco">
</div>
</body>
  <script type="text/javascript">

 /**======= Busca  =========*/
 var busca =
 {

  // Propriedade de busca
  info : '',

  // Chama API Google Maps
  googleapi: function(endereco)  {
 
   $.ajax({
    'url'   : 'http://maps.googleapis.com/maps/api/geocode/json?address='+encodeURIComponent(endereco)+'&sensor=false',
    'async'   : true,
    'success' : function(data)
    {
     busca.info = data.results[0].formatted_address;
    }
   });
   return busca.info;
  }
 };

 /**======= Evento  =========*/
 $(document).ready(function(){
  var endereco = busca.googleapi('Av. Paulista 100, São Paulo');
  $('#endereco').html(endereco);
 });
    </script>
</html>


Provavelmente o script acima não exibiu o endereço completo. Você faz noção do porquê?

Bem, o problema é que estamos injetando 'endereco', que é o retorno de 'busca.googleapi('Av. Paulista 100, São Paulo')' antes da resposta vinda da requisição Ajax, ou seja, o script roda mais rápido que a consulta aos servidores do Google, o que faz todo sentido.

Vamos confirmar isto exibindo a sequência de execução do script:



<html>
  <head>
 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
  </head>
  <body>
 <div id="endereco">
</div>
</body>
  <script type="text/javascript">

 /**======= Busca  =========*/
 var busca =
 {

  // Propriedade de busca
  info : '',

  // Chama API Google Maps
  googleapi: function(endereco)  {
 
   $.ajax({
    'url'   : 'http://maps.googleapis.com/maps/api/geocode/json?address='+encodeURIComponent(endereco)+'&sensor=false',
    'async'   : true,
    'success' : function(data)
    {
     busca.info = data.results[0].formatted_address;
     console.log('Google API Retorno');
    }
   });
   return busca.info;
  }
 };



 /**======= Evento  =========*/
 $(document).ready(function(){
  $('#endereco').html(busca.googleapi('Av. Paulista 100, São Paulo'));
  console.log('Modificação do DOM');
 });
    </script>
</html>


Provavelmente você viu a seguinte sequência no Console de seu Chrome:


  1. Modificação do DOM
  2. Google API Retorno 


Vemos que ele rodou primeiro o modificador e depois concluiu o retorno da consulta. O script é muito mais rápido que a consulta e, pela variável ainda estar vazia, ele imprime um vazio.

O script anterior funcionou simplesmente porque o modificador DOM estava em outro ponto do script, bem no método disparado após a consulta retornar seu valor e no ultimo caso o que o método disparado faz é apenas setar o valor na variável.

Se você digitar no console a seguinte linha:

busca.info

O endereço completo deve ser exibido.

Ou seja, a variável está com a informação, porém ela não chega a tempo de seguir a sequência do script e ser injetada no DOM. Percebemos assim que a sequência do programa é separada da consulta do Ajax, ou seja, as duas coisas ocorrem ao mesmo tempo (assíncrono).

Isto pode gerar muita confusão caso não se esteja acostumado a trabalhar com tarefas em paralelo. É comum o desenvolvedor reparar que os valores não estão sendo atualizados, ou que eles são atualizados apenas após uma segunda interação, justamente por causa disto, ou seja, o script funciona perfeitamente, só que as informações não chegam a tempo de serem exibidas. E lá se vão horas e horas até se perceber que o problema é tão somente a ordem em que as coisas ocorrem.

Bem, para resolvermos isto podemos setar a propriedade 'async' = false na chamada do Ajax. Isto fará com que todo o script do navegador 'pare' ou 'aguarde' até que a resposta seja retornada (síncrono), seja este retorno um dado ou um erro.


<html>
  <head>
 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
  </head>
  <body>
 <div id="endereco">
</div>
</body>
  <script type="text/javascript">

 /**======= Busca  =========*/
 var busca =
 {

  // Propriedade de busca
  info : '',

  // Chama API Google Maps
  googleapi: function(endereco)  {
 
   $.ajax({
    'url'   : 'http://maps.googleapis.com/maps/api/geocode/json?address='+encodeURIComponent(endereco)+'&sensor=false',
    'async'   : false,
    'success' : function(data)
    {
     busca.info = data.results[0].formatted_address;
     console.log('Google API Retorno');
    }
   });
   return busca.info;
  }
 };

 /**======= Evento  =========*/
 $(document).ready(function(){
  $('#endereco').html(busca.googleapi('Av. Paulista 100, São Paulo'));
  console.log('Modificação do DOM');
 });
    </script>
</html>


Esta configuração deve trazer como resultado o endereço completo impresso na página.

Então, por estes exemplos, podemos ter uma noção do que são requisições assíncronas e síncronas.

A partir disto podemos decidir o que melhor fazer para nossas aplicações, organização e etc. Poderíamos, sim, colocar tudo dentro do método 'success' e pronto, porém quando você tem uma infinidade de outros métodos necessitando deste valor pode não ser interessante para a organização do script jogar tudo lá, pode ser mais interessante setar o Ajax síncrono e, depois de receber o valor, seguir o programa.

Você também pode colocar as chamadas dos seus outros métodos dentro do 'success' e assim pode continuar utilizando o processo assíncrono normalmente.

Utilizar o Ajax no modo síncrono pode parecer heresia para alguns mas cabe a você decidir a melhor forma de o utilizar. Lembre-se que deve sempre haver um equilíbrio entre a organização do script, para que possa ser de fácil manutenção, e a sua eficiência. Em aplicações críticas muitas vezes temos de sacrificar uma em detrimento da outra.

É isso. Abraço a todos!

Comentários

Postagens mais visitadas deste blog

MySQL - Completando quantidades fixas de caracteres com as funções LPAD() e RPAD()

MySQL - Clonando tabelas na linha de comando

PHP - Gerando arquivo em UTF-8 com fwrite() e utf8_encode()