Introdução
O objectivo deste artigo é expor a funcionalidade de AJAX que o jQuery inclui, ao detalhe, e é também falar sobre as novas funcionalidades introduzidas pela versão 1.5 da framework, neste caso, os Deferreds. Nesta nova versão toda a funcionalidade de AJAX foi redesenhada, pelo que iremos entrar no tema das novas funcionalidades através da sua utilização no próprio AJAX e depois expandindo a outras alterações também com relevância.
Desenvolvimento
Começando pelo princípio, o método mais simples de efectuar um pedido AJAX em jQuery é utilizando a função jQuery.get:
var ajaxObj = $.get('ajax/mypage.aspx', function(data) { $('#ajaxDiv').html(data); alert('callback called!'); });
Este é o método mais simplificado, especificamos unicamente que URL irá retornar os dados, e a função de callback retorna-nos os dados e aí poderemos adicionar qualquer lógica necessária. Os dados que retornam do nosso pedido podem ser texto, JSON, XML ou JavaScript, e a função infere o tipo, pois neste caso não o estamos a especificar. Além da variável data
, poderíamos especificar outras duas variáveis na função de callback, a segunda seria o textStatus
do XHR (XMLHttpRequest) e a terceira seria o mesmo que o ajaxObj
irá conter, um jqXHR
(que passou a ser um jqXHR
a partir da versão 1.5, anteriormente era um XHR nativo).
Neste exemplo caso retornássemos HTML seria adicionado ao DOM como innerHTML
do objecto com o id ajaxDiv
e mostraria um alert, depois do pedido retornar com sucesso. O objecto jqXHR
implementa o interface de Promises (que iremos descortinar mais à frente na funcionalidade Deferreds do jQuery 1.5) e inclui toda a sua funcionalidade, pelo que inclui os métodos error()
, success()
e complete()
para acordar com os callbacks da função $.ajax
(que também iremos rever mais à frente) que aceitam uma função como parâmetro que é chamada quando o pedido terminar a sua execução, ou mesmo que estes callbacks sejam assignados após o pedido AJAX ter sido executado, estas são chamadas de qualquer modo, esta é uma das novidades deste interface de Promises, permite assignar callbacks à posteriori, o que não era possível nas versões anteriores às 1.5. Podemos ver aqui o exemplo de como assignar estes callbacks, e verificar que mesmo após o pedido ser completamente executado, assignando novos callbacks, estes executam de qualquer modo:
/*Assignar handlers imediatamente após executar o pedido e guardar numa var o objecto jqXHR*/ var xhrObj = $.get("ajax.aspx", function() { alert("sucesso!"); }) .success(function() { alert("novamente sucesso"); }) .error(function() { alert("erro"); }) .complete(function() { alert("pedido completo"); }); //alguma lógica adicional (...) /*adicionar outro callback de complexão aqui, e verificar que é executado mesmo que o pedido já tenha sido completamente efectuado anteriormente, devido às funcionalidades das Promises*/ xhrObj.complete(function(){ alert("completo novamente"); });
Em versões anteriores do jQuery, no caso de utilizarmos esta função get()
, se existisse um erro não conseguiríamos assignar um callback a não ser através da função global ajaxError()
, ou seja, não conseguiríamos ter um error handling local e objectivo, a não utilizando uma função mais genérica com a ajax()
. Uma ressalva, os pedidos efectuados com a função get()
que não sejam pedidos JSONP ou Script, não permitem cross-domain, como é usual. Se quisermos efectuar outro tipo de pedidos com a função get()
:
//fazer apenas o request e ignorar resultado $.get("ajax.aspx"); //passar parâmetros simples e ignorar resultados $.get("ajax.aspx", { tipo: "noticias", quantas: "10" } ); //passar arrays e ignorar resultados $.get("ajax.aspx", { 'valores[]': ["10", "20"]} ); //combinar parâmetros com callback $.get("ajax.aspx", { param1: "teste" }, function(data) { alert("callback executado!"); }); //receber um JSON já parsed $.get("ajax.aspx", { param1: "teste" }, function(data) { alert(data.prop1); // valor da variável data: { "prop1": "valor1" } });
Outra das funções para efectuar pedidos AJAX é a load()
:
// carrega o resultado no/s objecto do DOM especificado/s pelo selector $('#ajaxDiv').load('ajax.aspx', function() { alert('HTML carregado'); }); //carrega o resultado no/s objecto do DOM especificado/s pelo selector, mas apenas o que faz match com o selector passado ao lado do url $('#ajaxDiv').load('ajax.aspx #mainContent');
Também existe a possibilidade de enviar parâmetros, como o segundo parâmetro, à semelhança do get()
. Existe também a função post()
que funciona do mesmo exacto modo que a get()
mas ao invés de enviar os dados por HTTP GET, envia precisamente por HTTP POST. Caso o nosso objectivo seja exclusivamente obter JSON, existe uma função específica para tal, a getJSON()
, que tem algumas especificidades, tais como no caso de adicionarmos ao URL o texto callback=?
o pedido passa a ser tratado com um pedido JSONP, e não JSON, o que permite pedidos cross domain sem qualquer problema. O segundo parâmetro pode ser utilizado para enviar parâmetros, como nas outras funções.
$.getJSON('outputjson.json', function(data) { $('.result').html('<p>' + data.foo + '</p>' + '<p>' + data.baz[1] + '</p>'); }); //estrutura de JSON esperada: { "foo": "The quick brown fox jumps over the lazy dog.", "bar": "ABCDEFG", "baz": [52, 97] }
Função ajax()
Passando à função mais completa e talvez a mais utilizada, a função ajax()
, podemos definir o URL, e imensos settings, vou passar aqui pelos mais importantes:
async
: permite definir se o pedido é ou não executado assíncronamente;beforeSend(jqXHR, settings)
: este callback é executado imediatamente antes do pedido ser executado, e caso retornemosfalse
, o pedido não é executado;complete(jqXHR, textStatus)
: este callback é executado quando o pedido foi completamente executado, a partir da versão 1.5 podemos passar aqui um array de funções que serão todas executadas;data
: permite passar parâmetros no formato query string (valor1 =X&valor2=y...
);dataType
: permite definir exactamente que tipo de dados iremos receber,json
,script
,text
,html
,jsonp
,xml
(podemos passar múltiplos valores, por exemplojsonp xml
, para efectuar um pedido JSONP e converter para XML);success(data, textStatus, jqXHR)
: callback executado quando o pedido é retornado com sucesso;type
: tipo de pedido GET ou POST;url
: URL do pedido;error(jqXHR, textStatus, error)
: callback em caso de erro;statusCode
: definir um callback conforme o HTTP error code:$.ajax({ statusCode: {404: function() { alert('page not found'); } });
Podemos utilizar a função ajaxSetup()
para definir estes settings globalmente na nossa aplicação, sendo que depois podemos fazer override em cada caso aos settings que se alteram, centralizando tudo o que são settings transversais. Exemplos:
$.ajax({ type: "GET", url: "my.js", dataType: "script" }); //fazer o pedido por POST, enviando parámetros e com callback $.ajax({ type: "POST", url: "ajax.aspx", data: "nome=Ricardo&location=Lisboa", success: function(msg){ alert("Dados enviados: " + msg); } }); /*pedir a última versão de uma página, especificando que não queremos que o browser persista qualquer cache*/ $.ajax({ url: "teste.html", cache: false, success: function(html){ $("#resultado").append(html); } }); /*efectuar um pedido que ao estando o seu resultado a ser utilizado de imediato para assignar à variável html, devemos especifica que não pode ser assíncrono, pois caso contrário poderíamos tentar usar a variável html e esta não iria ter o valor esperado.*/ var html = $.ajax({ url: "page.aspx", async: false }).responseText; /*o mesmo caso que o anterior, mas aqui enviamos parâmetros e temos um callback de sucesso, e o o dataType é especificado. Ao utilizar o global a false, estamos a dizer explicitamente que os eventos globais de ajax não vão ser disparados, logo os seus callbacks não vão executar, isto caso estejam definidos via ajaxStart() e ajaxStop()*/ var bodyContent = $.ajax({ url: "script.aspx", global: false, type: "POST", data: ({id : this.getAttribute('id')}), dataType: "html", async:false, success: function(msg){ alert(msg); } } ).responseText;
Funcionalidades jQuery 1.5
Com esta especificação extensa do AJAX, vamos passar às novas funcionalidades do jQuery 1.5, começando pelos já mencionados Deferreds (Promises interface). Esta funcionalidade tem como objectivo fazer com que uma tarefa e a lógica executada após esta estar completa sejam desacoplados, quer isto dizer que podemos assignar múltiplos callbacks para o resultado de uma tarefa e mesmo após esta estar completa podemos continuar a adicioná-los e estes são executados do mesmo modo. Esta tarefa pode ser assíncrona ou não, nada obriga que o seja. Visto que o AJAX do jQuery 1.5 foi redesenhado para incluir os Deferreds, podemos usufruir deles directamente:
// este pedido é assíncrono por omissão var req = $.get('foo.htm') .success(function(response) { //em caso de sucesso }) .error(function() { //em caso de erro }); //isto até pode ser executado antes do get acima algumaFuncao(); /*definir algo mais a ser executado em caso de sucesso, que pode ou não já ter ocorrido, mas com os deferreds realmente não interessa, é executado de qualquer forma*/ req.success(function(response) { /*tomar alguma acção com a resposta isto vai ser executado quando o sucesso ocorrer, ou caso este já tenha ocorrido, é disparado de imediato, caso os outros callbacks de sucesso já tenham sido executados */ })
Deste modo podemos ver que já não estamos limitados a definir apenas um callback para error, sucesso e complexão, podemos definir quantos quisermos, e mais importante, quando quisermos!
Como podemos ver, deste modo podemos organizar o código de maneira diferente, até podemos criar uma abstracção à função de ajax
no contexto da nossa aplicação e ter funções para atribuição de callbacks, que são executados numa metodologia FIFO (First in first out). Não temos de definir callbacks de complexidade extrema pelo facto de apenas podermos definir um e até podemos começar a usar esta funcionalidade de um modo inteligente, para por exemplo, executar determinado código caso algumas funções AJAX tenham sido executadas com sucesso, isto de uma forma extremamente simples, utilizando a função $.then()
:
function doAjax() { return $.get('ajax.aspx'); } function doMoreAjax() { return $.get('ajax2.aspx'); } $.when( doAjax(), doMoreAjax() ) .then(function(){ console.log('Executado quando ambos os pedidos estão completos!'); }) .fail(function(){ console.log('Executado quando um ou mais pedidos falharam!'); });
Este código funciona porque o AJAX agora retorna uma promise()
que é utilizada para monitorizar o pedido assíncrono, esta promise() é um objecto apenas de leitura que existe no resultado da tarefa. Os Deferreds verificam a existência da função promise()
para determinar se um objecto é observable ou não, que é o que lhe permite funcionar como deferred. A função when()
aguarda pela execução das funções AJAX passadas por parâmetro e quando estas são executadas os métodos then()
e fail()
são executados, conforme o estado da tarefa. Importante referir novamente que os callbacks são executados pela ordem cujo são assignados a cada método.
Uma nota importante: os Deferreds aceitam ou funções ou arrays de funções, que nos permite definir conjuntos de comportamentos na nossa aplicação e passá-los genericamente, ao invés de passarmos apenas uma função isolada. Podemos verificar o estado de um deferred através das suas funções isRejected()
e isResolved()
. No caso do AJAX o que obtemos é um acesso a uma parte do deferred, visto que se tivéssemos acesso completo poderíamos controlar quando os callbacks são executados através da função resolve()
e poderíamos invocá-los antes dos pedidos realmente serem executados, o que iria quebrar a lógica, logo temos apenas acesso a uma parte do deferred, à promise()
, que é apenas de leitura, como já foi referido.
Em termos de métodos, os que utilizámos até agora foram o then()
, success()
e fail()
, também falámos do complete()
no caso de AJAX, mas existem mais métodos que podemos utilizar, especialmente no caso de estarmos a lidar com AJAX. O método escolhido depende exclusivamente do estado ao qual queremos fazer bind. Para todos os Deferreds existem os seguintes métodos:
then(doneCallbacks, failedCallbacks)
;done(doneCallbacks)
;fail(failCallbacks)
.
Os Deferreds de AJAX têm 3 métodos adicionais que se podem especificar, 2 dos quais invocam um dos acima especificados. Estes métodos específicos existem exclusivamente para não quebrar a compatibilidade com os nomes dos callbacks para AJAX que existiam nas versões anteriores de jQuery:
success(doneCallbacks)
maps todone()
error(failCallbacks)
maps tofail()
Existe também o método complete()
que é invocado após a função AJAX ser executada, retorne ou não erro. Ao contrário do success e do error o complete é um alias para o done, que é resolvido assim que o pedido AJAX termina, independentemente do seu resultado.
Um exemplo de utilização de Deferreds num bloco de código “típico”:
function getData(){ return $.get('/echo/html/'); } function showDiv() { var dfd = $.Deferred(); $('#foo').fadeIn( 1000, dfd.resolve ); return dfd.promise(); } $.when( getData(), showDiv() ) .then(function(result) { console.log('A animação e o pedido AJAX foram executados'); });
Podemos ver aqui a utilização dos Deferreds num bloco simples, e a sua explicação é muito simples: Na função showDiv
estamos a criar um objecto deferred novo, e retornamos a promise()
. Este Deferred como o código o mostra é resolvido assim que o fadeIn
terminar, pois o dfd.resolve
foi definido como callback deste fadeIn
. O getData
retorna um objecto compatível com Deferred (não exactamente igual, visto que é um AJAX e como já foi referido o AJAX não é um Deferred “simples”), e como o objecto retornado pelo getData
, tem o método promise
, é tratado com Deferred e o when()
aguarda que ambos estejam no estado resolved
, após estarem, executa o callback passado no método then()
e escreve na consola.
Conclusão
Neste artigo podemos observar todo o potencial do AJAX, a sua evolução nesta nova versão 1.5 e também a grande nova funcionalidade que são os Deferreds.
O jQuery está em constante evolução, esta é uma das novas features da versão 1.5, como foi demonstrado, tem um potencial enorme e uma abrangência e influência grandes, visto que até afectou áreas core da framework. Stay tuned!