Affrontez les tâches asynchrones avec les promesses JQuery

Les promesses sont une fonctionnalité jQuery passionnante qui facilite la gestion des événements asynchrones. Ils vous permettent d'écrire des rappels plus courts et plus clairs tout en maintenant la logique applicative de haut niveau à l'écart des comportements de bas niveau..

Une fois que vous avez compris les promesses, vous devrez les utiliser pour tous les types d’appels, des appels AJAX aux flux d’interface utilisateur. C'est une promesse!


Comprendre les promesses

Une fois qu'une promesse est résolue ou rejetée, elle reste dans cet état pour toujours.

Une promesse est un objet qui représente un événement ponctuel, généralement le résultat d'une tâche asynchrone comme un appel AJAX. Au début, une promesse est dans un en attendant Etat. Finalement, c'est soit résolu (signifiant que la tâche est terminée) ou rejeté (si la tâche a échoué). Une fois qu'une promesse est résolue ou rejetée, elle reste dans cet état pour toujours et ses rappels ne seront plus jamais déclenchés..

Vous pouvez joindre des rappels à la promesse, qui sera déclenchée lorsque la promesse sera résolue ou rejetée. Et vous pouvez ajouter plus de rappels à tout moment, même après la résolution / le rejet de la promesse! (Dans ce cas, ils vont tirer immédiatement.)

De plus, vous pouvez combiner logiquement les promesses en de nouvelles promesses. Cela rend trivialement facile l’écriture de code qui dit: «Quand tout cela est arrivé, faites cet autre chose».

Et c'est tout ce que vous devez savoir sur les promesses dans l'abstrait. Il existe plusieurs implémentations JavaScript parmi lesquelles choisir. Les deux plus remarquables sont le q de Kris Kowal, basé sur les spécifications CommonJS Promises / A, et jQuery Promises (ajouté dans jQuery 1.5). En raison de l'omniprésence de jQuery, nous allons utiliser sa mise en oeuvre dans ce tutoriel..


Faire des promesses avec $ .Deferred

Chaque promesse jQuery commence par un report. Un différé est simplement une promesse avec des méthodes qui permettent à son propriétaire de le résoudre ou de le rejeter. Toutes les autres promesses sont des copies "en lecture seule" d'un différé; nous en parlerons dans la section suivante. Pour créer un différé, utilisez le $ .Deferred () constructeur:

Un différé est juste une promesse avec des méthodes qui permettent à son propriétaire de le résoudre ou de le rejeter.

 var différé = nouveau $ .Deferred (); deferred.state (); // "en attente" deferred.resolve (); deferred.state (); // "résolu" deferred.reject (); // pas d'effet, car la promesse était déjà résolue

(Note de version: Etat() a été ajouté dans jQuery 1.7. Dans 1.5 / 1.6, utilisez isRejected () et est résolu().)

Nous pouvons obtenir une "pure" promesse en appelant un différé promettre() méthode. Le résultat est identique à celui différé, sauf que le résoudre() et rejeter() les méthodes manquent.

 var différé = nouveau $ .Deferred (); var promise = deferred.promise (); promise.state (); // "en attente" deferred.reject (); promise.state (); // "rejeté"

le promettre() La méthode existe uniquement pour l’encapsulation: si vous renvoyez un différé à partir d’une fonction, celui-ci pourrait être résolu ou rejeté par l’appelant. Mais si vous ne retournez que la pure promesse correspondant à celle différée, l'appelant peut uniquement lire son état et joindre des rappels. jQuery lui-même adopte cette approche, en renvoyant pure Promises de ses méthodes AJAX:

 var gettingProducts = $ .get ("/ produits"); gettingProducts.state (); // "en attente" gettingProducts.resolve; // indéfini

En utilisant le -ing La tension au nom d’une promesse indique clairement qu’elle représente un processus.


Modélisation d'un flux d'interface utilisateur avec des promesses

Une fois que vous avez une promesse, vous pouvez joindre autant de rappels que vous le souhaitez à l'aide du bouton terminé(), échouer(), et toujours() méthodes:

 promise.done (function () console.log ("Ceci s'exécutera si cette promesse est résolue.");); promise.fail (function () console.log ("Ceci s'exécutera si cette promesse est rejetée.");); promise.always (function () console.log ("Et cela fonctionnera dans les deux sens."););

Note de version: toujours() a été appelé Achevée() avant jQuery 1.6.

Il existe également un raccourci pour attacher tous ces types de rappels à la fois, puis():

 promise.then (doneCallback, failCallback, alwaysCallback);

Les rappels ont la garantie de fonctionner dans l’ordre dans lequel ils ont été attachés.

Un bon cas d’utilisation pour Promises représente une série d’actions potentielles de la part de l’utilisateur. Prenons un formulaire AJAX de base, par exemple. Nous voulons nous assurer que le formulaire ne peut être soumis qu'une seule fois et que l'utilisateur reçoit un accusé de réception lorsqu'il le soumet. De plus, nous voulons que le code décrivant le comportement de l'application soit séparé du code qui touche le balisage de la page. Cela facilitera grandement les tests unitaires et minimisera la quantité de code à modifier si nous modifions notre mise en page..

 // Logique d'application var submitingFeedback = new $ .Deferred (); soumittingFeedback.done (fonction (entrée) $ .post ("/ feedback", entrée);); // interaction DOM $ ("# # feedback"). Submit (function () submitFeedback.resolve ($ ("textarea", this) .val ()); return false; // empêche le comportement par défaut du formulaire); submittingFeedback.done (function () $ ("# conteneur"). append ("

Merci pour votre avis!

"););

(Nous profitons du fait que les arguments passés à résoudre()/rejeter() sont transmis textuellement à chaque rappel.)


Emprunter des promesses d'avenir

tuyau() renvoie une nouvelle promesse qui imitera toute promesse retournée par l'un des tuyau() rappels.

Notre code de formulaire de commentaires a l'air bien, mais il y a place à l'amélioration de l'interaction. Plutôt que de penser avec optimisme à la réussite de notre appel POST, nous devons d’abord indiquer que le formulaire a été envoyé (avec un spinner AJAX, par exemple), puis indiquer à l’utilisateur si la soumission a réussi ou échoué lorsque le serveur répond..

Nous pouvons le faire en joignant des rappels à la promesse retournée par $ .post. Mais là est un défi: nous devons manipuler le DOM à partir de ces rappels, et nous nous sommes engagés à garder notre code touchant le DOM hors de notre code logique d'application. Comment pouvons-nous faire cela, lorsque la promesse POST est créée dans un rappel de logique d'application?

Une solution consiste à "transmettre" les événements de résolution / rejet de la promesse POST à ​​une promesse qui se situe dans l'étendue externe. Mais comment pouvons-nous le faire sans plusieurs lignes de stéatite douce (promise1.done (promise2.resolve);…) Heureusement, jQuery fournit une méthode à cet effet: tuyau().

tuyau() a la même interface que puis() (terminé() rappeler, rejeter() rappeler, toujours() rappeler; chaque rappel est facultatif), mais avec une différence cruciale: puis() renvoie simplement la promesse à laquelle il est attaché (pour l'enchaînement), tuyau() renvoie une nouvelle promesse qui imitera toute promesse retournée par l'un des tuyau() rappels. En bref, tuyau() est une fenêtre sur le futur, nous permettant d’attacher des comportements à une promesse qui n’existe même pas encore.

Voici notre nouveau et amélioré code de formulaire, avec notre promesse POST attachée à une promesse appelée sauvegardeFeedback:

 // Logique d'application var submitingFeedback = new $ .Deferred (); var savingFeedback = submitFeedback.pipe (fonction (entrée) return $ .post ("/ feedback", entrée);); // interaction DOM $ ("# # feedback"). Submit (function () submitFeedback.resolve ($ ("textarea", this) .val ()); return false; // empêche le comportement par défaut du formulaire); submittingFeedback.done (function () $ ("# conteneur"). append ("
");); savingFeedback.then (function () $ (" # conteneur "). append ("

Merci pour votre avis!

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

Une erreur s'est produite lors de la connexion au serveur..

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

Trouver l'intersection des promesses

Une partie du génie des promesses tient à leur nature binaire. Parce qu'ils n'ont que deux états éventuels, ils peuvent être combinés comme des booléens (bien que des booléens dont les valeurs ne soient pas encore connues).

L'équivalent de Promise de l'intersection logique (ET) est donné par $ .when (). Compte tenu d'une liste de promesses, quand() renvoie une nouvelle promesse qui obéit à ces règles:

  1. Quand tout des promesses données sont résolues, la nouvelle promesse est résolue.
  2. Quand tout de la promesse donnée est rejetée, la nouvelle promesse est rejetée.

Chaque fois que vous attendez plusieurs événements non ordonnés, vous devriez envisager d’utiliser quand().

Les appels AJAX simultanés sont un cas d'utilisation évident:

 $ ("# conteneur"). append ("
"); $ .when ($. get (" / encryptedData "), $ .get (" / encryptionKey ")). then (function () // les deux appels AJAX ont réussi, function () // un des appels AJAX a échoué, function () $ ("# conteneur"). remove (". spinner"););

Un autre cas d'utilisation consiste à permettre à l'utilisateur de demander une ressource déjà disponible ou non. Par exemple, supposons que nous ayons un widget de discussion que nous chargeons avec YepNope (voir Chargement de scripts faciles avec yepnope.js).

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", complet: loadingChat.resolve); var launchingChat = new $ .Deferred (); $ ("# launchChat"). cliquez sur (launchingChat.resolve); launchingChat.done (function () $ ("# chatContainer"). append ("
");); $ .when (loadingChat, launchingChat) .done (function () $ (" # chatContainer "). remove (". spinner "); // démarrer le chat);

Conclusion

Les promesses se sont révélées être un outil indispensable dans la lutte en cours contre le code spaghetti asynchrone. En fournissant une représentation binaire des tâches individuelles, ils clarifient la logique de l'application et réduisent le nombre de points chauds suivis par l'état.

Si vous souhaitez en savoir plus sur Promises et sur d'autres outils permettant de préserver votre intégrité dans un monde de plus en plus asynchrone, consultez mon prochain livre électronique: Async JavaScript: Recipes for Event-Driven Code (publication prévue en mars)..