Olá, pessoal!
Sabemos que o JavaScript executa tudo de forma assíncrona, dessa forma é necessário que pensemos com um pouco mais de cautela ao desenvolver aplicações em JavaScript/jQuery para que não criemos bugs, por vezes, difíceis de captar futuramente.
Mesmo tomando todos os cuidados ao construir nossos scripts, existem momentos em que precisamos executar alguma ação, mas dependemos que outra ação já tenha sido executada por completo.
Para resolver isto podemos utilizar o objeto Deferred do jQuery.
Consideremos que temos um cenário de um sistema que calcula juros incidente em um determinado valor.
Utilizarei a função setTimeout com timer de 3 segundos para simular a execução de um processamento. Portanto temos:
<script type="text/javascript"> function buscaValorBase(){ setTimeout(function(){ return 1000; },3000); } function aplicaJuros(valorBase, jurosReajuste){ return valorBase + (valorBase * (jurosReajuste/100)); } $(document).ready(function(){ var valorBase; var valorReajustado; var jurosReajuste = 15;//porcentagem de juros valorBase = buscaValorBase(); valorReajustado = aplicaJuros(valorBase, jurosReajuste); alert(valorReajustado); }); </script>
Para o cenário acima, o alert nos retornaria NaN porque ao executar a funcao aplicaJuros o valor que está na variável valorBase é undefined ao invés de 1000 como esperado. A causa disso é que no momento em que o JavaScript está executando este alert a função buscaValorBase chamada anteriormente ainda está executando, portanto ao efetuarmos cálculos com undefined o retorno será sempre Not a Number (NaN).
Vamos então introduzir na função buscaValorBase o objeto Deferred do jQuery:
<script type="text/javascript"> function buscaValorBase(){ var valor = 1000; var df = new $.Deferred(); setTimeout(function(){ df.resolve(valor); },3000); return df.promise(); }
Vejamos o que acontece:
- Na linha 4 instanciamos um novo objeto Deferred na variável df;
- Na linha 7 estamos executando o método resolve do objeto Deferred, passando como parâmetro o valor a ser retornado da função. É basicamente este método que "diz" que o processamento da função terminou;
- Na linha 10 estamos dando um return do método promise. É este método que "entrega" o retorno da função para quem está o aguardando;
Agora vamos ver como chamar a função buscaValorBase de uma forma que se comunique com o Deferred e nos retorne o valor correto:
<script type="text/javascript"> function buscaValorBase(){ var valor = 1000; var df = new $.Deferred(); setTimeout(function(){ df.resolve(valor); },3000); return df.promise(); } function aplicaJuros(valorBase, jurosReajuste){ return valorBase + (valorBase * (jurosReajuste/100)); } $(document).ready(function(){ var valorBase; var valorReajustado; var jurosReajuste = 15;//porcentagem de juros $.when(buscaValorBase()).done(function(valorBase){ valorReajustado = aplicaJuros(valorBase, jurosReajuste); alert(valorReajustado); }); }); </script>
Agora fizemos o seguinte:
- Na linha 22 com o uso do jQuery when damos start na função buscaValorBase passando ela como parâmetro;
- Ainda na linha 22 instanciamos em done uma função de callback recebendo em valorBase o valor retornado pela função buscaValorBase. Este callback será executado quando a promise da função buscaValorBase informar o término da execução;
- Na linha 23, já dentro da função de callback estamos chamando a função valorReajustado para calcular o reajuste e depois dando o alert deste valor;
Como de forma geral nem sempre os juros são fixos no mercado, o juros incidente para reajuste pode ser dinâmico. Neste caso, podemos trabalhar com mais de uma função simultânea para uma mesma chamada síncrona.
Vamos então criar uma função que efetue a busca do índice de juros:
<script type="text/javascript"> function buscaValorBase(){ var valor = 1000; var df = new $.Deferred(); setTimeout(function(){ df.resolve(valor); },3000); return df.promise(); } function buscaJuros(){ var valor = 15; var df = new $.Deferred(); setTimeout(function(){ df.resolve(valor); },5000); return df.promise(); } function aplicaJuros(valorBase, jurosReajuste){ return valorBase + (valorBase * (jurosReajuste/100)); } $(document).ready(function(){ var valorBase; var valorReajustado; var jurosReajuste; $.when(buscaValorBase(), buscaJuros()).done(function(valorBase, jurosReajuste){ valorReajustado = aplicaJuros(valorBase, jurosReajuste); alert(valorReajustado); }); }); </script>
Dessa forma temos:
- Na linha 13 criamos uma função que fará a busca pelo juros incidente. Estamos considerando que esta função demorará mais (2 segundos) para retornar do que a buscaValorBase;
- Na linha 33 estamos passando a função buscaJuros como segundo parâmetro para o jQuery when;
- Ainda na linha 33, estamos recebendo no callback como segundo parâmetro o retorno da promise da segunda função (buscaJuros) que passamos para o jQuery when;
- Na linha 34 calculamos normalmente o resultado;
Passando simultaneamente mais de uma função para o jQuery when, o callback no método done só será executado quando todas as funções passadas para o when finalizarem sua execução.
Perfeito! Agora seu código nunca mais terá problemas de assincronicidade!