Discutir tareas asincrónicas con JQuery Promises

Las promesas son una característica interesante de jQuery que facilita la administración de eventos asíncronos. Le permiten escribir devoluciones de llamada más cortas y claras y mantener la lógica de la aplicación de alto nivel separada de los comportamientos de bajo nivel.

Una vez que entienda Promesas, querrá usarlas para todo, desde las llamadas AJAX hasta el flujo de UI. Es una promesa!


Entendiendo promesas

Una vez que una Promesa se resuelve o rechaza, permanecerá en ese estado para siempre.

Una promesa es un objeto que representa un evento único, generalmente el resultado de una tarea asíncrona como una llamada AJAX. Al principio, una promesa está en una pendiente estado. Eventualmente, es bien resuelto (lo que significa que la tarea está hecha) o rechazado (si la tarea falló). Una vez que una Promesa se resuelve o rechaza, permanecerá en ese estado para siempre y sus devoluciones de llamada nunca volverán a activarse..

Puede adjuntar devoluciones de llamada a la Promesa, que se activará cuando la Promesa se resuelva o rechace. Y puede agregar más devoluciones de llamada cuando lo desee, incluso después de que la Promesa haya sido resuelta / rechazada. (En ese caso, dispararán inmediatamente.)

Además, puede combinar Promesas lógicamente en nuevas Promesas. Eso hace que sea trivialmente fácil escribir código que diga: "Cuando todas estas cosas hayan sucedido, haz esta otra cosa".

Y eso es todo lo que necesita saber acerca de las promesas en abstracto. Hay varias implementaciones de JavaScript para elegir. Las dos más notables son las q de Kris Kowal, basadas en las promesas CommonJS Promises / A y jQuery Promises (agregadas en jQuery 1.5). Debido a la ubicuidad de jQuery, usaremos su implementación en este tutorial.


Haciendo promesas con $. Diferido

Cada jQuery Promise comienza con un Aplazado. Un Aplazado es solo una Promesa con métodos que le permiten a su propietario resolverlo o rechazarlo. Todas las demás Promesas son copias "de solo lectura" de un Aplazado; Hablaremos de eso en la siguiente sección. Para crear un Aplazado, use el $. Diferido () constructor:

Un Aplazado es solo una Promesa con métodos que le permiten a su propietario resolverlo o rechazarlo.

 var aplazado = nuevo $. Aplazado (); diferido.estado (); // "pendiente" deferred.resolve (); diferido.estado (); // "resuelto" deferred.reject (); // sin efecto, porque la Promesa ya estaba resuelta

(Nota de versión: estado() Fue agregado en jQuery 1.7. En 1.5 / 1.6, usar se rechaza() y esta resuelto().)

Podemos obtener una Promesa "pura" llamando a un Aplazado promesa() método. El resultado es idéntico al Aplazado, excepto que el resolver() y rechazar() faltan métodos.

 var aplazado = nuevo $. Aplazado (); promesa de var = diferido.promiso (); promesa.estado (); // "pendiente" deferred.reject (); promesa.estado (); // "rechazado"

los promesa() el método existe únicamente para la encapsulación: si devuelve un Aplazado desde una función, el llamante podría resolverlo o rechazarlo. Pero si solo devuelve la Promesa pura correspondiente a ese Aplazado, la persona que llama solo puede leer su estado y adjuntar devoluciones de llamada. jQuery toma este enfoque y devuelve Promesas puras de sus métodos AJAX:

 var gettingProducts = $ .get ("/ products"); gettingProducts.state (); // "pendiente" gettingProducts.resolve; // indefinido

Utilizando la -En g el tiempo en el nombre de una Promesa deja claro que representa un proceso.


Modelando un flujo de UI con promesas

Una vez que tenga una Promesa, puede adjuntar tantas devoluciones de llamada como desee utilizando el hecho(), fallar(), y siempre() métodos:

 promise.done (function () console.log ("Esto se ejecutará si se resuelve esta Promesa.");); promise.fail (function () console.log ("Esto se ejecutará si esta Promesa es rechazada.");); promise.always (function () console.log ("Y esto se ejecutará de cualquier manera."););

Nota de versión: siempre() fue referido como completar() antes de jQuery 1.6.

También hay una taquigrafía para adjuntar todos estos tipos de devoluciones de llamada a la vez, entonces():

 promise.then (doneCallback, failCallback, alwaysCallback);

Se garantiza que las devoluciones de llamada se ejecutan en el orden en que se adjuntaron en.

Un gran caso de uso para Promesas es representar una serie de acciones potenciales por parte del usuario. Tomemos un formulario básico de AJAX, por ejemplo. Queremos asegurarnos de que el formulario solo pueda enviarse una vez, y que el usuario reciba algún reconocimiento cuando envíe el formulario. Además, queremos mantener el código que describe el comportamiento de la aplicación separado del código que toca el marcado de la página. Esto hará que las pruebas unitarias sean mucho más fáciles y minimizará la cantidad de código que se debe cambiar si modificamos el diseño de nuestra página..

 // Lógica de aplicación var submittingFeedback = new $ .Deferred (); submitingFeedback.done (función (entrada) $ .post ("/ feedback", entrada);); // Interacción DOM $ ("# feedback"). Submit (function () submitingFeedback.resolve ($ ("textarea", this) .val ()); return false; // evita el comportamiento predeterminado de los formularios); submitingFeedback.done (function () $ ("# container"). append ("

Gracias por tus comentarios!

"););

(Estamos aprovechando el hecho de que los argumentos pasaron a resolver()/rechazar() son reenviados literalmente a cada devolución de llamada.)


Préstamos prometidos del futuro

tubo() devuelve una nueva Promesa que imitará cualquier Promesa devuelta desde uno de los tubo() devoluciones de llamada.

El código de nuestro formulario de comentarios se ve bien, pero hay espacio para mejorar la interacción. En lugar de suponer con optimismo que nuestra llamada POST tendrá éxito, primero debemos indicar que el formulario ha sido enviado (con un control de giro AJAX, por ejemplo), luego decirle al usuario si el envío se realizó correctamente o no cuando el servidor responde..

Podemos hacer esto adjuntando devoluciones de llamada a la Promesa devuelta por $ .post. Pero ahí radica un desafío: tenemos que manipular el DOM desde esas devoluciones de llamada, y nos hemos comprometido a mantener nuestro código que toca el DOM fuera de nuestro código lógico de aplicación. ¿Cómo podemos hacer eso, cuando se crea la Promesa POST dentro de una devolución de llamada de lógica de aplicación??

Una solución es "reenviar" los eventos de resolución / rechazo de la Promesa POST a una Promesa que vive en el ámbito externo. Pero, ¿cómo lo hacemos sin varias líneas de caldo suave (promise1.done (promise2.resolve);...)? Afortunadamente, jQuery proporciona un método para exactamente este propósito: tubo().

tubo() tiene la misma interfaz que entonces() (hecho() llamar de vuelta, rechazar() llamar de vuelta, siempre() llamar de vuelta; cada devolución de llamada es opcional), pero con una diferencia crucial: Mientras entonces() simplemente devuelve la Promesa a la que se adjunta (para el encadenamiento), tubo() devuelve una nueva Promesa que imitará cualquier Promesa devuelta desde uno de los tubo() devoluciones de llamada En breve, tubo() es una ventana al futuro, que nos permite adjuntar comportamientos a una Promesa que ni siquiera existe todavía.

Aquí está nuestro nuevo y mejorado código de formulario, con nuestra Promesa POST canalizada a una Promesa llamada savingFeedback:

 // Lógica de aplicación var submittingFeedback = new $ .Deferred (); var savingFeedback = submittingFeedback.pipe (función (entrada) return $ .post ("/ feedback", entrada);); // Interacción DOM $ ("# feedback"). Submit (function () submitingFeedback.resolve ($ ("textarea", this) .val ()); return false; // evita el comportamiento predeterminado de los formularios); submitingFeedback.done (function () $ ("# container"). append ("
");); savingFeedback.then (function () $ (" # container "). append ("

Gracias por tus comentarios!

");, function () $ (" # container "). append ("

Se ha producido un error al contactar con el servidor..

");, function () $ (" # container "). remove (". spinner "););

Encontrar la intersección de promesas

Parte del genio de las promesas es su naturaleza binaria. Debido a que solo tienen dos estados eventuales, se pueden combinar como booleanos (aunque booleanos cuyos valores aún no se conozcan).

La Promesa equivalente a la intersección lógica (Y) es dado por $ .cuando (). Dada una lista de promesas, cuando() Devuelve una nueva Promesa que obedece estas reglas:

  1. Cuando todos de las Promesas dadas se resuelven, la nueva Promesa se resuelve.
  2. Cuando alguna de las Promesas dadas es rechazada, la nueva Promesa es rechazada.

Siempre que esté esperando que ocurran múltiples eventos desordenados, debe considerar usar cuando().

Las llamadas AJAX simultáneas son un caso de uso obvio:

 $ ("# contenedor"). añadir ("
"); $ .when ($. get (" / encryptedData "), $ .get (" / encryptionKey ")). Luego (function () // ambas llamadas AJAX han sido exitosas, function () // one de las llamadas AJAX ha fallado, función () $ ("# container"). remove (". spinner"););

Otro caso de uso es permitir que el usuario solicite un recurso que puede o no haber estado ya disponible. Por ejemplo, supongamos que tenemos un widget de chat que estamos cargando con YepNope (consulte Carga fácil de scripts con yepnope.js)

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", complete: loadingChat.resolve); var launchingChat = new $ .Deferred (); $ ("# launchChat"). haga clic en (launchingChat.resolve); launchingChat.done (function () $ ("# chatContainer"). append ("
");); $ .when (loadingChat, launchingChat) .done (function () $ (" # chatContainer "). remove (". spinner "); // start chat);

Conclusión

Las promesas han demostrado ser una herramienta indispensable en la lucha en curso contra el código de espagueti asíncrono. Al proporcionar una representación binaria de tareas individuales, aclaran la lógica de la aplicación y reducen la placa de preparación del estado..

Si desea obtener más información sobre Promesas y otras herramientas para preservar su cordura en un mundo cada vez más asíncrono, eche un vistazo a mi próximo libro electrónico: JavaScript asíncrono: recetas para el código dirigido por eventos (que saldrá en marzo).