Compiti Async di Wrangle con Promesse di JQuery

Le promesse sono un'entusiasmante funzione jQuery che semplifica la gestione degli eventi asincroni. Ti consentono di scrivere callback più chiari e più brevi e di mantenere la logica applicativa di alto livello separata da comportamenti di basso livello.

Una volta compreso Promises, ti consigliamo di utilizzarli per qualsiasi cosa, dalle chiamate AJAX al flusso dell'interfaccia utente. Questa è una promessa!


Comprendere le promesse

Una volta che una promessa è stata risolta o respinta, rimarrà in tale stato per sempre.

Un Promesso è un oggetto che rappresenta un evento occasionale, in genere il risultato di un'attività asincrona come una chiamata AJAX. All'inizio, una promessa è in a in attesa di stato. Alla fine, è entrambi risoluto (significa che il compito è finito) o respinto (se l'attività è fallita). Una volta che una promessa viene risolta o rifiutata, rimarrà in tale stato per sempre e le sue richiamate non spareranno mai più.

Puoi allegare i callback alla Promessa, che si attivano quando la Promessa viene risolta o respinta. E puoi aggiungere più richiami quando vuoi - anche dopo che la Promessa è stata risolta / respinta! (In tal caso, spareranno immediatamente.)

Inoltre, puoi combinare promesse logicamente in nuove promesse. Ciò rende banalmente facile scrivere codice che dice "Quando tutte queste cose sono successe, fai quest'altra cosa".

E questo è tutto ciò che devi sapere sulle promesse in astratto. Esistono diverse implementazioni di JavaScript tra cui scegliere. I due più importanti sono il q di Kris Kowal, basato sulle specifiche di CommonJS Promises / A e jQuery Promises (aggiunto in jQuery 1.5). A causa dell'ubiquità di jQuery, useremo la sua implementazione in questo tutorial.


Fare promesse con $ .Deferred

Ogni jQuery Promise inizia con un differito. Un differito è solo una promessa con metodi che consentono al suo proprietario di risolverlo o rifiutarlo. Tutte le altre promesse sono copie "di sola lettura" di un differito; ne parleremo nella prossima sezione. Per creare un differito, usa il $ .Deferred () costruttore:

Un differito è solo una promessa con metodi che consentono al suo proprietario di risolverlo o rifiutarlo.

 var deferred = new $ .Deferred (); deferred.state (); // "in sospeso" deferred.resolve (); deferred.state (); // "risolto" deferred.reject (); // nessun effetto, perché la Promessa era già stata risolta

(Nota sulla versione: stato() è stato aggiunto in jQuery 1.7. In 1.5 / 1.6, usare isRejected () e isResolved ().)

Possiamo ottenere una promessa "pura" chiamando un Deferred promettere() metodo. Il risultato è identico al differito, tranne che il risolvere() e rifiutare() mancano metodi.

 var deferred = new $ .Deferred (); var promise = deferred.promise (); promise.state (); // "in sospeso" deferred.reject (); promise.state (); // "respinto"

Il promettere() il metodo esiste puramente per l'incapsulamento: se si restituisce un rinviato da una funzione, potrebbe essere risolto o rifiutato dal chiamante. Ma se si restituisce solo la Promessa pura corrispondente a quella Rinviata, il chiamante può solo leggere il suo stato e allegare i callback. jQuery stesso prende questo approccio, restituendo pure Promesse dai suoi metodi AJAX:

 var gettingProducts = $ .get ("/ prodotti"); gettingProducts.state (); // "in sospeso" getProducts.resolve; // non definito

Usando il -ing teso nel nome di una promessa chiarisce che rappresenta un processo.


Modellare un flusso dell'interfaccia utente con promesse

Una volta che hai una Promessa, puoi allegare il numero di callback che desideri usando fatto(), fallire(), e sempre() metodi:

 promise.done (function () console.log ("Questo verrà eseguito se questa promessa è stata risolta.");); promise.fail (function () console.log ("Questo verrà eseguito se questa Promessa viene rifiutata.");); promise.always (function () console.log ("E questo funzionerà in entrambi i modi."););

Nota sulla versione: sempre() è stato indicato come completare() prima di jQuery 1.6.

C'è anche una scorciatoia per allegare tutti questi tipi di callback contemporaneamente, poi():

 promise.then (doneCallback, failCallback, alwaysCallback);

Le callback sono garantite per essere eseguite nell'ordine in cui sono state allegate.

Un ottimo caso d'uso per Promises rappresenta una serie di potenziali azioni da parte dell'utente. Prendiamo ad esempio un modulo AJAX di base. Vogliamo assicurarci che il modulo possa essere inviato una sola volta e che l'utente riceva una conferma quando invia il modulo. Inoltre, vogliamo mantenere il codice che descrive il comportamento dell'applicazione separato dal codice che tocca il markup della pagina. Ciò renderà il test delle unità molto più semplice e ridurrà al minimo la quantità di codice che deve essere modificata se modifichiamo il layout della pagina.

 // Logica dell'applicazione var submittingFeedback = new $ .Deferred (); submittingFeedback.done (function (input) $ .post ("/ feedback", input);); // DOM interaction $ ("# feedback"). Submit (function () submittingFeedback.resolve ($ ("textarea", this) .val ()); restituisce false; // impedisce il comportamento del modulo predefinito); submittingFeedback.done (function () $ ("# container"). append ("

Grazie per il tuo feedback!

"););

(Stiamo approfittando del fatto che gli argomenti sono passati risolvere()/rifiutare() vengono inoltrati testualmente a ciascun callback.)


Promettiamo prestiti dal futuro

tubo() restituisce una nuova Promessa che imiterà qualsiasi Promessa restituita da uno dei tubo() callback.

Il nostro codice di modulo di feedback sembra buono, ma c'è spazio per migliorare l'interazione. Piuttosto che assumendo ottimisticamente che la nostra chiamata POST avrà successo, dovremmo prima indicare che il modulo è stato inviato (con uno spinner AJAX, per esempio), quindi dire all'utente se l'invio è riuscito o meno quando il server risponde.

Possiamo farlo collegando i callback alla Promessa restituita da $ .post. Ma qui sta una sfida: dobbiamo manipolare il DOM da quei callback e abbiamo promesso di mantenere il nostro codice DOM-toccante dal nostro codice logico dell'applicazione. Come possiamo farlo quando la POST Promise viene creata all'interno di un callback della logica dell'applicazione?

Una soluzione è di "inoltrare" gli eventi di risoluzione / rifiuto dalla Promessa POST a una Promessa che vive nello scopo esterno. Ma come lo facciamo senza parecchie linee di blando scaldavivande (promise1.done (promise2.resolve);...)? Per fortuna, jQuery fornisce un metodo esattamente per questo scopo: tubo().

tubo() ha la stessa interfaccia di poi() (fatto() richiama, rifiutare() richiama, sempre() richiama; ogni callback è facoltativo), ma con una differenza cruciale: While poi() restituisce semplicemente la Promessa a cui è associata (per il concatenamento), tubo() restituisce una nuova Promessa che imiterà qualsiasi Promessa restituita da uno dei tubo() callback. In breve, tubo() è una finestra sul futuro, che ci consente di collegare i comportamenti a una promessa che ancora non esiste ancora.

Ecco il nostro nuovo e migliorato codice del modulo, con il nostro POST Promise convogliato per una promessa chiamata savingFeedback:

 // Logica dell'applicazione var submittingFeedback = new $ .Deferred (); var savingFeedback = submittingFeedback.pipe (function (input) return $ .post ("/ feedback", input);); // DOM interaction $ ("# feedback"). Submit (function () submittingFeedback.resolve ($ ("textarea", this) .val ()); restituisce false; // impedisce il comportamento del modulo predefinito); submittingFeedback.done (function () $ ("# container"). append ("
");); savingFeedback.then (function () $ (" # container "). append ("

Grazie per il tuo feedback!

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

Si è verificato un errore durante il contatto con il server.

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

Trovare l'intersezione delle promesse

Parte del genio delle Promesse è la loro natura binaria. Poiché hanno solo due stati finali, possono essere combinati come i booleani (sebbene i booleani i cui valori potrebbero non essere ancora noti).

L'equivalente Promise dell'intersezione logica (E) è dato da $ .Quando (). Dato un elenco di promesse, quando() restituisce una nuova promessa che obbedisce a queste regole:

  1. quando tutti delle promesse date vengono risolte, la nuova promessa viene risolta.
  2. quando qualunque delle promesse date viene respinta, la nuova promessa viene respinta.

Ogni volta che aspetti che si verifichino eventi multipli non ordinati, dovresti prendere in considerazione l'utilizzo quando().

Le chiamate simultanee AJAX sono un caso d'uso ovvio:

 $ ( "# Contenitore"). Append ("
"); $ .quando ($. get (" / encryptedData "), $ .get (" / encryptionKey ")). then (function () // entrambe le chiamate AJAX sono state eseguite con successo, function () // one delle chiamate AJAX è fallito, function () $ ("# container"). remove (". spinner"););

Un altro caso d'uso è consentire all'utente di richiedere una risorsa che potrebbe essere o non essere già disponibile. Ad esempio, supponiamo di avere un widget di chat che stiamo caricando con YepNope (vedi Easy Script Loading with yepnope.js)

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

Conclusione

Le promesse si sono dimostrate uno strumento indispensabile nella lotta continua contro il codice spaghetti asincrono. Fornendo una rappresentazione binaria delle singole attività, chiariscono la logica dell'applicazione e riducono il boilerplate di monitoraggio dello stato.

Se desideri saperne di più su Promises e altri strumenti per preservare la tua sanità mentale in un mondo sempre più asincrono, controlla il mio prossimo eBook: Async JavaScript: ricette per codice Event-Driven (in uscita a marzo).