¡Hola! Me parece que andas usando un bloqueador de anuncios =( ¿Nos desbloqueas? ¿Por qué?
F13

Funcion 13

Comprendiendo promesas y deferreds en jQuery

Cuando comencé con la aplicación de BuSeViCi, sabía que tendría muchos retos por delante y quizá fue ese uno de los principales motivos por los que comencé a hacerla. Mi principal objetivo era aprender sobre el API de Google Maps, extraer datos de servidores y utilizar algunas divertidas funciones de HTML5 como localStorage.

Pero llegó un punto, en el que tropecé con una enorme piedra en el camino. ¿Cómo podía trabajar con todos los datos que estaban siendo obtenidos en una llamada asíncrona? Imaginad el caso, le pido al servidor que me mande todas las paradas de autobús de una determinada línea y, conforme las vaya obteniendo, que vaya añadiéndolas al mapa pero, una vez que las tenga todas haga algo fantástico.

El código inicial que uno con poca experiencia se plantea es algo similar a esto:

var estaciones = [];  
$.get({
  url : ‘/estaciones.php’,
  success : function (estaciones_ajax) {
    estaciones = estaciones_ajax
  }
});

funcionQueInsertaEstacionesEnMapa(estaciones);  

Lo que acabas observando es que la última parte del código, se ejecuta antes de que la función AJAX haya terminado. Entonces, ¿cómo lo soluciono?

Podrías comenzar a meter todo el código en la función success, pero y ¿si necesitas también que otra función sea ejecutada cuando la función que acabas de incrustarle a success acabe? La solución sin duda no es seguir engrosando la función callback.

Una posible solución sería el método publicación/suscripción (Pub/Sub) y, aunque no es algo en lo que vaya a profundizar, es un método totalmente válido. No obstante, jQuery da una manera mucho más elegante y natural de solventar este problema, y es conocida como deferred.

Comprendiendo qué es deferred y las promesas

Comencemos diciendo que un objeto deferred es tan solo un objeto. La promesa (promise) en sí, es un objeto que representa un evento en el tiempo, como la llegada de datos de una petición asíncrona o el fin del procesamiento de todos los datos obtenidos en una función asíncrona. Un objeto promise es una versión de solo lectura de un objeto deferred.

Los deferred comienzan en estado pendiente, y es lógico si lo pensáis, pudiendo pasar a estado resuelto (la tarea se completó) o rechazado (si es que falló). En el momento en que un deferred cambie su estado a resuelto o rechazado… ¡no podrá cambiar nunca más!

Veamos cómo trabajar de manera muy básica con deferreds:

var deferred = $.Deferred(),  
promesa = deferred.promise();  
deferred.state(); // “pending” -> “pendiente”  
deferred.reject(); promesa.state(); // “rejected” -> “rechazada”  
deferred.resolve(); // Ya no hace nada, la promesa ya fue rechazada  

Quizá os estéis preguntando cómo aplicar esto en la práctica. Ahora vamos a verlo pero quizá no sepáis que jQuery ya usa deferreds de manera interna:

var obteniendoEstaciones = $.get(‘/estaciones.php’);  
console.log(obteniendoEstaciones.state()); // “pending” -> “pendiente”  

jQuery usa promesas para manejar las peticiones AJAX, y estas siempre devuelven una promesa de la petición, a la que podemos suscribirnos.

¿Cuándo se ha cumplido mi promesa?

Una vez que tienes una promesa, puedes asociarle tantas funciones como quieras usando los métodos done(), fail() y always():

promesa.done(function() {  
  // Esta función se ejecutará cuando la promesa esté resuelta
  alert(‘He cumplido con la promesa, maestro’);
});
promesa.fail(function() {  
  // Esta función se ejecutará cuando la promesa falle
  alert(‘He fallado en mi empresa, maestro’);
});
promesa.always(function() {  
  //Esta función se ejecutará siempre, falle o resuelva
  alert(‘He terminado con la promesa.’);
});
promesa.fail(function() {  
  //Esta función también se ejecutará (además de la otra) si la promesa falla
  // Castigar esbirro
});

Aunque también podemos usar esta notación, con la función then():

promesa.then(callbackDone, callbackFail, progressFilter);  

Las promesas reciben como argumento lo que se les pase. Por ello, una función done asociada a una promesa de una función get(), recibirá como parámetros los datos recibidos de la petición. Además, por si te lo estabas preguntando, las funciones se ejecutan en el orden en que se asocian a la promesa.

Las promesas son geniales para representar una serie de acciones del servidor que puedan venir en cadena y separar claramente la lógica de la aplicación con las interacciones con el DOM.

// Lógica de la aplicación
var obteniendoEstaciones = $.Deferred();  
var anadirAFavoritos = $.Deferred();  
anadirAFavoritos.done(function(estacion) {  
  $.post(‘anade_favoritos.php’, estacion);
});
// Interacciones con la web
obteniendoEstaciones.done(function(estaciones) {  
  //Función mágica que añade las estaciones al mapa
});
$(‘.favoritos’).click(function(e) {
  anadirAFavoritos.resolve($(this).data(‘estacion’), $(this));
  e.preventDefault();
});
anadirAFavoritos.done(function(estacion, elemento) {  
  elemento.addClass(‘marcado’);
  //Resalta la estrella en amarillo
});

¿Y si tengo varias promesas/deferreds?

La mejor parte de las promesas es que funcionan en gran medida como elementos booleanos porque tienen un estado resuelto (podríamos asemejarlo a true) y uno rechazado (que sería false).

Si queremos que algo se ejecute cuando dos o más promesas sean resueltas, nos valdremos de la función $.when() que dada una lista de Promesas, devuelve una nueva que sigue el siguiente patrón:

  • Cuando todas las promesas hayan sido resueltas, la promesa devuelta será resuelta.
  • Cuando alguna de las promesas haya fallado, la promesa devuelta será rechazada.

Siguiendo este patrón, podríamos usarlo si por ejemplo estamos cargando varios elementos de forma asíncrona:

$(‘#contenido’).append(‘ < div class = ”cargando” > ’);
var obteniendoTweets = $.get(‘/ultimosTweets.php’), obteniendoMensajesFB = $.get(‘/ultimosMensajesFB.php’);  
$.when(obteniendoTweets, obteniendoMensajesFB).then(function() {
  // Ambas habrán tenido éxito
}, function() {
  // Alguna ha fallado
}, function() {
  // Pase lo que pase, siempre vamos a quitar el elemento cargando
  $(‘#contenido’).remove(‘.cargando’);
});

Conclusión

Las promesas/deferred son una herramienta indispensable para el código asíncrono gracias a su naturaleza binaria que representa el estado de una tarea en concreto, ayudándonos a separar la lógica de la aplicación de manera muy sencilla, en pequeñas porciones de código más legibles.

Programador Front-end en First + Third y Potato. Trabajando con JavaScript y HTML5 desde el corazón de Sevilla.

Comentarios ¡Únete a la conversación!