El nuevo objeto Deferred en jQuery 1.5

02 Feb 2011

Tras el lanzamiento oficial de la nueva versión 1.5 de jQuery, ya podemos comentar algunas de las novedades más interesantes que presenta. La mayoría de estas mejoras vienen tras algunas críticas sobre la dificultad de utilizar jQuery en grandes arquitecturas frente a otras librerías como YUI.

En este aspecto resultan especialmente interesantes la reescritura casi completa del módulo AJAX y la implementación del nuevo objeto Deferrered. En este artículo vamos a revisar ambos prestando una mayor atención al segundo y analizando sus implicaciones en proyectos de cierta envergadura.

Deferred callback management system

jQuery.Deferred es una de las nuevas funcionalidades introducidas en la versión 1.5 de jQuery y que supone una utilidad de los objetos mediante la cual se pueden registrar multiples callbacks (rellamadas) y transmitir los estados success (éxito) o failure (fallo) desde cualquier función ya sea síncrona o asíncrona.

Esto quiere decir que mediante este nuevo objeto, podemos separar la lógica de los callbacks de nuestras funciones de la propia función. También permite elaborar múltiples callbacks y asociarlos a una función incluso aunque ésta ya haya sido completada. Las implicaciones de esto en el manejo de peticiones Ajax, son impresionantes.

Tomemos un ejemplo típico de llamada Ajax en jQuery:

$.get(
  'foo.php',
  function( data ){ // Callback
    console.log( 'The server response is: ' + data );
  }
);

Hasta ahora, el callback de nuestra llamada lo integrábamos dentro de la misma petición Ajax y, por supuesto, no podíamos añadir una nueva acción una vez ésta había sido realizada.

Modifiquemos el ejemplo anterior para poder manipular de forma independiente los eventos success o error:

var ajaxReq = $get( 'foo.php' )
  .success( function( data ){
    // Do something with the response 'data'
  }).error( function(){
    // Something was failed
  });
 
console.log( 'This code is written before ajaxReq is complete' );

En este caso, nuestra petición Ajax posée dos funciones específicas según el éxito o no de la petición. Sin embargo, al tratarse de una llamada asíncrona, el código posterior se ejecutará independientemente de que se haya o no completado la llamada.

Si precisamos añadir una nueva acción que dependa de la respuesta del servidor, utilizamos el nuevo método Deferred ya implementado en el objeto Ajax:

ajaxReq.success( function( data ){
  console.log( 'Do something more with the response - data' );
});

Ahora no estamos limitados a incluir todas las acciones a realizar (callbacks) dentro de la propia llamada Ajax; en su lugar, podemos declarar nuevas acciones en cualquier momento gracias a que jQuery recordará y monitorizará el estado de cada respuesta en un objeto.

Más flexibilidad. jQuery.when() y promise

Sin embargo, es frecuente en nuestras aplicaciones el lanzar varias peticiones simultáneas y encontrarnos en la necesidad de asociar un evento conjunto a todas ellas. Por ejemplo, podemos necesitar que todas las peticiones hayan sido respondidas correctamente antes de continuar con el flujo de nuestra aplicación.

Imaginemos que nuestra aplicación permite a los usuarios reservar entradas para una sesión de cine en su sala favorita con un solo click incluyendo el pago de las mismas.

Para ello, el flujo natural es enviar una petición Ajax para comprobar que los asientos solicitados están disponibles junto a otra petición a la pasarela de pago previamente asociada. Solo en caso de que ambas peticiones se hayan resuelto satisfactoriamente, añadimos el registro a nuestra base de datos para un control posterior y creamos el ticket que se imprimirá en pantalla.

Al depender de dos llamadas distintas, el nuevo objeto Deferred, nos evita el anidar varias peticiones una dentro de otra:

function cinemaReq(){
  return $.get( 'checkTicket.php' );
}
function bankReq(){
  return $.get( 'checkBankValidation.php' );
}
 
$.when( cinemaReq(), bankReq() )
  .then( function(){
    console.log( 'Everything was completed' );
  } )
  .done( function(){
    console.log( ' Everything was OK!' );
  })
  .fail( function(){
    console.log( 'Something was failed' );
  });

Hemos separado completamente la lógica de nuestra aplicación del flujo de las peticiones consiguiendo un código mucho más limpio y mantenible.

Esto es posible gracias al nuevo método promise que devuelven los también nuevos objetos Ajax: $.when comprueba que el objeto posea el método promise y espera a que las peticiones Ajax se hayan realizado para asociar nuevos callbacks según la respuesta obtenida. Las posibilidades son:

.then( doneCallbacks, failedCallbacks )
.done( doneCallbacks )
.fail( failedCallbacks )
  • La primera, espera a que todas las peticiones se hayan completando independientemente del estado devuelto.
  • La segunda, precisa que todas las peticiones hayan sido completadas con éxito para ejecutar su contenido (success).
  • La tercera sólo se dispara si alguna de las peticiones ha fallado (error).

Cuando tratamos con peticiones Ajax, podemos utilizar alternativas semánticas para conseguir el mismo comportamiento:

.complete( doneCallbacks, failedCallbaks ) // Alternative to 'then'
.success( doneCallbacks ) // Alternative to 'done'
.error( failCallbacks ) // Alternative to 'fail'

Por lo tanto, podemos resumir lo anterior indicando que los siguientes ejemplos son equivalentes:

$.get( 'foo.html' ).done( fn );
$.get( 'foo.html' ).success( fn );
$.get( 'foo.html', fn );

Creando nuestras propios objetos Deferred

Como hemos visto, Deferred busca el método promise para asociar funciones personalizadas a un objeto, por lo que podemos crear nuestras propias implementaciones si recurrimos a él:

function cinemaReq(){
  return $.get( 'checkTicket.php' );
}
function showCinemaMap(){
  var ownDeferred = $.Deferred();
  // Code for create a cinema map
  // Manipulating DOM, etc...
  return ownDeferred.promise();
}
 
$.when( cinemaReq(), showCinemaMap() )
  .then( function( data ){
    console.log( 'Ajax request and showCinemaMap are both done.' );
  } )
  .done( function( data ){
    console.log( 'Ajax request and showCinemaMap are both OK!.' );
  } )
  .fail( function( data ){
    console.log( 'Ajax request failed!' );
  })

Hemos creado una instancia del objeto Deferred dentro de nuestra función showCinemaMap(), lo que permite trabajar con sus métodos. De esta forma, podemos devolver el valor de promise que $.when monitorizará según el estado.

La flexibilidad de $.Deferred() permite añadir directamente callbacks a nuestras funciones independientes al resto del flujo:

function showCinemaMap(){
  var ownDeferred = $.Deferred();
  ownDeferred.done( function(){
    console.log( 'All actions inside this function have been done.' );
  });
  // Code for create a cinema map
  // Manipulating DOM, etc...
  return ownDeferred.promise();
}

De esta forma, podemos encadenar callbacks tanto a funciones de forma individual como en grupo.

Conclusión

El objeto Deferred, ofrece un amplio abanico de posiblidades a la hora de delegar acciones según el estado de nuestras funciones. La ventaja más inmediata radica en la separación entre la lógica y los eventos, creando con ello una arquitectura más limpia y escalable.

Por otro lado, la posibilidad de crear colas con funciones personalizadas permite una gran flexibilidad en campos como las cada vez más frecuentes peticiones Ajax. En lugar de organizar nuestro código en función de lo que devuelva un servidor, anidando llamadas y respuestas, creamos las funciones pertinentes que asociamos al nuevo objeto Deferred. Esto garantiza que dichas funciones serán ejecutadas según su contexto sin tener en cuenta temas como la sincronicidad.

Es cierto que adoptar esta nueva metodología a la hora de diseñar nuestras aplicaciones requiere haber asumido y revisado todo su potencial. Sin embargo, una vez asimilado, las ventajas justifican ampliamente el esfuerzo invertido.

Más:

Solo un marciano comentando!

  1. edgar

    Lo necesitaba. Muchas gracias!!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *