Si has usado alguna vez $.ajax en jQuery habrás notado que para pasar una función que sea ejecutada cuando la petición sea realizada se utiliza la función ‘then’ o ‘done’ en cascada, justo después de la llamada a $.ajax. Esto es así porque $.ajax devuelve un objeto denominado ‘deferred’, y que da mucho más juego de lo que parece a simple vista.

Un objeto deferred es una referencia a algo que aún no está disponible, pero en un futuro lo estará. Se podría entender como un almacén de funciones para un determinado objeto, y aunque he introducido este artículo citando el ejemplo de $.ajax, lo cierto es que se pueden utilizar para cualquier cosa.

El objeto deferred básicamente consiste en una bandera que indica el estado del objeto (resuelto, erróneo o en progreso) y varias funciones que se ejecutarán cuando el objeto cambie de estado (callbacks). Cuando resolvemos un objeto deferred, estamos indicando que el recurso ya está disponible, y por lo tanto se ejecutarán las funciones que antes hemos asociamos a ese cambio de estado.

Los objetos deferred también suelen denominarse promises o futures en otros contextos o lenguajes, aunque básicamente se refieren a lo mismo.

Uso de Deferred

Vayamos a un ejemplo sencillo de uso, donde podemos ver como creamos un objeto Deferred y llamamos a la función done pasándole un callback. La función ‘done’ nos permite especificar una función que será ejecutada cuando el objeto sea resuelto. Al resolver un objeto, podemos especificar un valor de retorno. Este valor será recibido por todos los callbacks asociados al referred.

// Creamos un objeto Deferred
var miDeferred = $.Deferred();

// Asociamos una funcion que se ejecutara cuando
// el objeto miDeferred sea resuelto
miDeferred.done(function (ret) {
  alert("El deferred se resolvio con: " + ret);
});

// Resolvemos el objeto miDeferred, por lo tanto
// se ejecutaran los callbacks asociados a el
miDeferred.resolve('Mensaje de prueba');

Un objeto Deferred puede tener tantos callbacks como queramos. Podríamos llamar al método ‘done’ del objeto deferred varias veces con distintas funciones, y todas ellas se ejecutarían cuando el objeto se resuelva.

También es importante tener en cuenta que podemos añadir callbacks llamando a las distintas funciones de deferred antes o después de que el objeto sea resuelto. Si lo hacemos después de que sea resuelto, la función callback será ejecutada inmediatamente.

Operaciones avanzadas con deferreds

jQuery nos permite filtrar o encadenar deferreds usando ‘pipe’. Esta función nos permite especificar un callback que se ejecutará cuando el objeto se resuelva, y nos devolverá un nuevo deferred. El valor de retorno del nuevo deferred será el resultado de ejecutar la función que pasamos a pipe pasándole el valor de retorno del primer deferred. Seguramente esto sea más fácil de ver con un ejemplo:

// Creamos un objeto Deferred
var deferredUno = $.Deferred();

// Usamos pipe para crear un objeto Deferred nuevo
// derivado de deferredUno. Cuando deferredUno sea resuelto
// deferredDos se resolvera automaticamente, multiplicando
// por dos lo que sea que se coloque como valor de retorno en
// deferredUno
var deferredDos = deferredUno.pipe(function (ret) {
  return (ret * 2);
})

// Creamos un callback que se ejecutara cuando se
// resuelva deferredDos 
deferredDos.done(function (ret) {
  alert(ret);
});

// Resolvemos deferredUno
deferredUno.resolve('2');

También es importante conocer la función $.when, que devuelve un objeto Deferred que será resuelto cuando todos los objetos Deferred que le pasemos se resuelvan.

// Hacemos peticiones con $.ajax, lo que devuelve objetos deferred
var peticion1 = $.ajax({ ... });
var peticion2 = $.ajax({ ... });

// $.when devuelve un objeto deferred que se resolvera
// automaticamente cuando peticion1 y peticion2 sean resultos
$.when(peticion1, peticion2).then(function (ret1,ret2) {
  // Aqui en la variable ret1 disponemos del resultado de
  // la peticion1, y en ret2 del resultado de la peticion2
  alert("Ambas peticiones finalizaron!");
});

Podríamos añadir callbacks a los objetos peticion1 y peticion2 si también quisiéramos notificaciones de la finalización de las peticiones de forma individual.

Para mas información sobre Deferred puedes echarle un ojo a su API

Ejemplo con $.ajax

¿Alguna vez te has encontrado con que continuamente haces una llamada a $.ajax, procesas el resultado de la misma manera y después realizas un paso distinto? Algo así:

$.ajax({
  url: '...',
  data: { ... },
  type: 'json'
}).then(function (result) {
  var data = result.map(function (element) {
    // Procesamos element para crear data
  });

  // Aqui hacemos algo dependiente de data, pero
  // distinto en cada situación
});

La desventaja de esto es obviamente la duplicidad del código. Los parámetros y la manera en la que se procesa la variable ‘result’ está duplicado a lo largo del código. Cuanto más usamos este fragmento de código, más duplicamos el código.

Una primera mejora sería concentrar el código que procesa los datos en una función, de modo que eliminamos la duplicidad de ese código, pero aún tenemos duplicados todos los parámetros que pasamos a $.ajax: la url, el type y los parámetros de data.

Una manera de solucionar esto es llamar a $.ajax y procesar la respuesta de la petición usando ‘pipe’, de manera que nuestra función acabará devolviendo un objeto deferred y así podremos llamar a las funciones ‘done’, ‘fail’ y ‘then’ tal y como lo hacíamos con $.ajax

function myAjax() {
  return $.ajax({
    url: '...',
    data: { ... },
    type: 'json'
  }).pipe(function (result) {
    return result.map(function (element) {
      // Procesamos element para crear data
    });
  });
}

myAjax().done(function (data) {
  // Aqui hacemos algo dependiente de data, pero
  // distinto en cada situación
});

Con esto eliminamos la duplicidad tanto de los parámetros de $.ajax como de la función que procesa el resultado del servidor.

Conclusión

Comprender y utilizar los objetos deferred cuando programamos con jQuery puede darte mucha versatilidad a la hora de programar. Devolver objetos deferred en tus funciones que utilizan recursos asíncronos te dará consistencia y facilitará la comprensión de tu API para alguien que ya está acostumbrado a jQuery. Por último, no olvides que puedes encadenar deferreds para ejecutar funciones cuando varios deferreds se resuelvan.