O objeto Deferred da jQuery 1.5
Publicado em: 2011-02-1 — 38.974 visualizacoes
Nota inicial: A recém lançada versão 1.5 da biblioteca jQuery trouxe como sua maior novidade a criação do objeto Deferred
para requisições AJAX. Trata-se de um objeto contendo vários métodos que visam, em última análise, a permitir que se atrele múltiplos callbacks aos diversos eventos que ocorrem ao curso de uma requisição sem preocupações com sincronismo.
Eric Hynds um desenvolvedor front-end de Boston publicou uma máteria explicando essa nova funcionalidade que apresento aos meus leitores devidamente traduzida.
Deferred é uma funcionalidade nova da biblioteca jQuery 1.5 e destina-se a dissociar os resultados de uma requisição da própria requisição. Este é um conceito já conhecido em cenários JavaScript; as bibliotecas Mochikit e Dojo já o implementam há algum tempo, mas como analisado por Julian Aubourg’s a implementação de Deferred na jQuery foi um passo lógico e natural. Essa funcionalidade permite que se atrele callbacks múltiplos aos eventos de uma requisição e, mais ainda, qualquer um dos callbacks pode ser atrelado quando a requisição se completa. A requisição em questão pode ser, não necessariamente, assíncrona.
E, mais ainda, Deferred está incorporada ao método $.ajax()
, ou seja, está disponível automaticamente. Manipuladores de eventos podem ser atrelados como mostrado a seguir:
// // // $.get, é uma requisição AJAX assíncrona por padrão. var req = $.get('foo.htm').success(function( response ){ // fazer algo com response }).error(function(){ // fazer algo se a requisição falhar }); // código a ser executado antes que $.get() tenha terminado doSomethingAwesome(); // código a ser executado no sucesso da requisição, podendo ou não // ser disparado já. como Deferred está incorporado a $.ajax isso não faz diferença. req.success(function( response ){ // mais código a ser executado no sucesso da requisição // dispara normalmente com o disparo de success ou imediatamente // caso o callback anterior de success já tenha disparado. });
Agora não estamos mais limitados a um único manipulador para cada evento: sucesso, error, ou tarefa completada. Em lugar de funções callback únicas agora é possível criar uma fila de callbacks auto gerenciada.
Como mostrado no exemplo anterior agora é possível atrelar callbacks depois que a requisição AJAX se completou ou depois que um evento de observação (sucesso, erro) tenha se completado. Isso é muito bom para a organização do código; os dias de desenvolvimento de callbacks intrincados terminaram. Funciona mais ou menos como se fosse possível organizar uma fila de callbacks.
Suponha um cenário no qual desejamos chamar uma função logo após várias requisições AJAX concorrente terem sido completadas. Isso é possível com o uso do método $.when()
de Deferred como mostrado a seguir:
function doAjax(){ return $.get('foo.htm'); } function doMoreAjax(){ return $.get('bar.htm'); } $.when( doAjax(), doMoreAjax() ).then(function(){ console.log( 'Disparei assim que AMBAS as requisições se completaram' ); }).fail(function(){ console.log( 'Disparei se uma ou mais requisições falharam' ); });
Isso funciona porque agora todos os métodos AJAX da jQuery retornam um objeto contendo uma "promise", que é usada para monitorar o andamento da requisição assíncrona. A "promise" é uma saída do tipo somente leitura para o andamento da requisição. Deferred usa o método promise()
para determinar se um objeto da requisição é observável ou não. O método $.when()
espera pela execução das requisições AJAX e quando terminadas os callbacks a ele atrelados com uso dos métodos .then()
e .fail()
são devidamente disparados (nos casos de sucesso ou falha). Os callbacks disparam na ordem em que foram definidos.
E as vantagens não param aí: todos os métodos de Deferred aceitam funções ou arrays de funções. Isso permite que se projete comportamentos a serem disparados com uma chamada ou várias chamadas, conforme nossas necessidades.
$.ajax()
retorna um objeto contendo um pacote de métodos de Deferred. Eu citei promise()
, mas existem ainda os métodos then()
, success()
, error()
e muitos outros. Você não tem acesso ao pacote completo de objetos Deferred, mas somente aos métodos de atrelamento para promise()
e aos métodos isRejected()
e isResolved()
, que são usados para verificar o estado de Deferred.
Mas, por que não temos acesso à todos os objetos? Se tivéssemos acesso seria possível ir além talvez até pragmaticamente quot;resolve" o Deferred, fazendo callbacks serem executados antes que a requisição AJAX se completasse. Isso contraria um paradigma.
Atrelando callbacks
Nos exemplos anteriores eu usei os métodos then()
, success()
, e fail()
para atrelar callbacks com Deferred, mas existem outros métodos disponíveis especialmente quando se trabalha com AJAX Deferred. O método a usar depende do estado ao qual o callback deva ser atrelado.
Disponíveis para todos os Deferred (AJAX, $.when
, e os criados manualmente):
.then( doneCallbacks, failedCallbacks ) .done( doneCallbacks ) .fail( failCallbacks )
AJAX Deferred têm três métodos adicionais que mapeiam para um dos métodos mostrados anteriomente. Eles foram disponibilizados como alternativas semanticas para métodos já existentes
// // // "success" e "error" mapeiam para "done" e "fail" respectivamente. .success( doneCallbacks ) .error( failCallbacks )
Você pode atrelar o manipulador complete
que dispara independentemente de a requisição ser bem sucedida ou falhar. Diferente de success
e error
complete
é um alias para done
em um objeto Deferrred separado. Esse objeto Deferred separado é criado internamente por $.ajax()
e resolvido tão logo a requisição se complete independentemente do seu resultado.
.complete( completeCallbacks )
Assim, os três exemplos mostrados a seguir são equivalentes (A palavra success (sucesso) é muito mais amigável que done (feito) no contexto de AJAX. Você não acha?)
$.get("/foo/").done( fn ); // é o mesmo que: $.get("/foo/").success( fn ); // é o mesmo que: $.get("/foo/", fn );
Criando seus Deferred
Sabemos que $.ajax
e $.when
implementam suas API Deferred internamente, mas você pode criar suas implementações personalizadas:
function getData(){ return $.get('/foo/'); } function showDiv(){ var dfd = $.Deferred(); $('#foo').fadeIn( 1000, dfd.resolve ); return dfd.promise(); } $.when( getData(), showDiv() ).then(function( ajaxResult ){ console.log('A animação e a requisição AJAX, ambas terminaram'); // 'ajaxResult' é a resposta do servidor });
Na função showDiv()
estou criando um novo objeto Deferred com a finalidade de produzir uma animação é retornar promise. O Deferred se resolve (pense em dequeue()
se você conhece os métodos de enfileiramento da jQuery) após o completamento de uma chamada para fadeIn()
. Entre o tempo de retorno de promise e a resolução de Deferred um callback com uso do método then()
é atrelado ao sucesso das duas requisições assíncronas. Assim, quando ambas as requisições se completam o callback é disparado.
getData()
retorna um método promise()
que permite a $.when()
observar sua resolução. Os passos que criamos manualmente para retornar promise na função showDiv()
são manipulados internamente por $.ajax()
e $.when()
.
Adiando Deferred
Podemos ir além do que foi mostrado no exemplo anterior atrelando callbacks individuais para getData()
e showDiv()
, bem como definindo suas promises individuais dentro de um "master" Deferred.
Se você pretende que algo seja executado quando do sucesso de getData()
e de showDiv()
(independentemente um do outro), e também que algo seja executado quando do sucesso de ambos getData()
e showDiv()
combinados, simplesmente atrele callbacks aos seus Deferred individuais e junte-os com uso de $.when
:
function getData(){ return $.get('/foo/').success(function(){ console.log('Dispara depois do sucesso da requisição AJAX'); }); } function showDiv(){ var dfd = $.Deferred(); dfd.done(function(){ console.log('Dispara depois do sucesso da animação'); }); $('#foo').fadeIn( 1000, dfd.resolve ); return dfd.promise(); } $.when( getData(), showDiv() ).then(function( ajaxResult ){ console.log('Dispara depois do sucesso de AMBAS as requisições showDiv() e AJAX'); // 'ajaxResult' é a resposta do servidor });
Encadeamento
Callbacks Deferred podem ser encadeados uma vez que promise seja retornada da função. A seguir mostramos um exemplo real (via @ajpiano!)
function saveContact( row ){ var form = $.tmpl(templates["contact-form"]), valid = true, messages = [], dfd = $.Deferred(); /* validação client-side */ if( !valid ){ dfd.resolve({ success: false, errors: messages }); } else { form.ajaxSubmit({ dataType: "json", success: dfd.resolve, error: dfd.reject }); } return dfd.promise(); }; saveContact( row ).then(function(response){ if( response.success ){ // salvar; rejoice } else { // validação falha // mostrar saída de response.errors }}).fail(function(err) { // requisição AJAX falhou });
A função saveContact()
valida o formulário e salva o resultado na variável valid
. Se a validação falha Deferred se resolve com um objeto contendo o valor booleano false
para success e uma mensagem criada com um array de erros. Se a validação passa Deferred se resolve com uma resposta de sucesso da requisição AJAX. O manipulador fail()
responde com erros 404, 500, e outros erros HTTP que possam impedir o sucesso da requisição AJAX.
Tarefas não observáveis
Deferred são particularmente úteis quando a lógica de execução é ou não assíncrona é a você interessa não considerar essa condição no código. Sua tarefa pode retornar não apenas promise, mas também string, objeto ou outro tipo qualquer.
No exemplo a seguir na primeira vez que a "aplicação roda" e um link é clicado uma requisição AJAX grava no servidor e retorna o timestamp atual. O timestamp é gravado em cache tão logo a requisição se completa. A aplicação considerada somente a primeira vez que o link é clicado e nos cliks subsequentes o timestamp é lido do cache em lugar de ser feita uma nova requisição ao servidor.
function startTask( element ){ var timestamp = $.data( element, 'timestamp' ); if( timestamp ){ return timestamp; } else { return $.get('/start-task/').success(function( timestamp ){ $.data( element, 'timestamp', timestamp ); }); }} $('#launchApplication').bind('click', function( event ){ event.preventDefault(); $.when( startTask(this) ).done(function( timestamp ){ $('#status').html( '<p>Você começou essa tarefa em: ' + timestamp + '</p>'); }); loadApplication(); });
Quando $.when()
que seu primeiro argumento não tem um promise (e em consequência não é observável), ele cria um novo objeto Deferred resolve ele com uso de data e retorna o método promise()
de Deferred.
Um possível bug, que deverá ser corrigido na próxima versão da biblioteca e o fato de que você não pode adiar um objeto que implementa seu próprio método promisse()
. Deferred são detectados pela presença do método promise()
, mas jQuery não faz nenhuma verificação se o objeto retornado por promisse()
é usável. Assim o exemplo a seguir acusa um erro de sintaxe:
var obj = { promise: function(){ // faça algo }}; $.when( obj ).then( fn );
Conclusão
Deferred introduz uma nova e robusta maneira de se projetar requisições assíncronas. Em lugar de se preocupar em como orgaanizar a lógica de callbacks em um único callback você agora pode definir várias ações individuais de callback em fila e estar seguro que elas serão executadas no contexto em que foram inseridas sem se preocupar com sincronismo. O assunto abordado nessa matéria está longe de ter esgotado todas a possibilidades dessa nova funcionalidade da biblioteca jQuery, mas uma vez que você tenha entendido como ela funciona estará apto a escrever código assíncrono com muito maior simplicidade.
Muito obrigado a Steven Black pela edição dessa matéria. Obrigado ao pessoal da #jquery-dev por ter me ajudado a entender Deferred. E obrigado a Adam que criou um belo exemplo sobre Deferred.
Nota em 23/01/2012: O William Bruno publicou uma matéria contendo exemplos práticos do uso do objeto Deferred em Exemplo de uso jQuery.Deferred – simples.
Desenvolvimento com Padrões Web? Adquira os livros do Maujor
Visite o site dos livros.
Esta matéria foi publicada em: 2011-02-1 (terça-feira). Subscreva o feed RSS 2.0 para comentários.
Comente abaixo, ou link para https://www.maujor.com/blog/2011/02/01/o-objeto-deferred-da-jquery-1-5/trackback no seu site.
[…] sempre indico o ótimo tutorial do Maujor: http://www.maujor.com/blog/2011/02/01/o-objeto-deferred-da-jquery-1-5, porém talvez por ser tão completo, e ter tantas informações, alguns iniciantes podem não […]
Muito interessante, antes eu fazia assim: http://forum.imasters.com.br/topic/453492-retorno-de-funcao-javascript-com-requisicao-ajax/
Ola maujor, muito bom o tópico, mas, tenho uma duvida. como esta o jquery 1.5 em relação a compatibilidade com internetExplorer 6 e outros navegadores antigos ? Obrigado.
Otimo blog.Parabéns pelo blog.
Parabéns pelo blog.
Parabéns!Otimo blog
Parabéns!Otimo trabalho.
Maujor sempre com posts muito bem feitos, explicando e exemplificando de forma didática. Parabéns!
E sobre o Deferred, muito bom para ter que controlar diversas requisições ajax, sem precisar usar contadores malucos para saber quando todas acabaram (ou outra técnica também maluca)…
Excelente matéria, ambas! Este objeto promete ser muito útil =)
Muito bom o seu trabalho.
Muito bom…
Facilita muito se você quiser carregar variáveis de configurações em arquivos distinitos. (como internacionalização ou configurações default).
Boa maujor , sempre ajudando a galera aqui brasileira com dados em português , muito obrigado ! grande abraço !
[…] This post was mentioned on Twitter by Revista Wide, Web Feeds Brasil. Web Feeds Brasil said: O objeto Deferred da jQuery 1.5 http://bit.ly/hSRyWw […]
Muito interessante, as capacidades foram ampliadas ainda mais!!! o/
Bem últil essa implementação, deixou o código bem elegante.