jQuery 1.5 e AJAX

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 retornemos false, 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 exemplo jsonp 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:

  1. success(doneCallbacks) maps to done()
  2. error(failCallbacks) maps to fail()

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!

Publicado na edição 28 (PDF) da Revista PROGRAMAR.