Comprensione di jQuery.Deferred e Promise
Indice
In questo post ci sono molti esempi eseguibili in Javascript, ma potrebbe non funzionare su un lettore RSS.
JQuery 1.5 aveva introdotto il concetto di “azioni differite“. Trovo questo concetto molto potente quando si lavora con Javascript e AJAX, e penso che sia un punto di svolta per il modo in cui siamo abituati a scrivere il codice asincrono in js.
Di solito, il modo in cui siamo abituati a trattare con codice asincrono in Javascript è passando un callback come argomento alla funzione:
#
jQuery.ajax({
url: "/echo/json/",
data: {
json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani"})} ,
type: "POST",
success: function(person){
alert(person.firstName + " salvata.");
},
error: function(){
alert("error!");
}
});
#
success
ed error
) al metodo jQuery.Ajax
utilizzando la sintassi dell’oggetto-letterale supportato dal metodo.
Questo lavoro, che non è un’interfaccia standard, richiede una modifica all’interno del metodo post e non si può registrare dei callback multipli.
il primo è il metodo
always()
che può essere utilizzato per eseguire una funzione indipendentemente dal fatto che l’oggetto differito sia risolto o rifiutato;il secondo è il nuovo metodo
pipe()
, che può essere utilizzato per filtrare gli oggetti passivi sia quando si risolvono o quando falliscono.Il metodo restituisce un valore filtrato che può essere passato al metodo
done()
o fail()
dell’oggetto differito o al nuovo oggetto promise.L’oggetto differito è abbastanza facile da capire ma potente. Ha due metodi importanti:
- resolve
- reject
E ha tre importanti “eventi” o modi di allegare una retrorichiamata:
- done
- fail
- always
così, in un esempio fondamentalmente stupido sarà qualcosa del genere:
#
var deferred = jQuery.Deferred();
deferred.done(function(value) {
alert(value);
});
deferred.resolve("Ciao Mondo");;
#
Un altro punto interessante è che se si allega una richiamata ad una differita già risolta, si ottiene che viene eseguito immediatamente:
#
var deferred = jQuery.Deferred();
deferred.resolve("Ciao Mondo");
deferred.done(function(value) {
alert(value);
});
#
promise()
restituisce l’oggetto promise che può essere utilizzato per eseguire del codice arbitrario una volta che tutte le azioni di ogni elemento della collezione, collegate al metodo, siano state completate. Per impostazione predefinita l’oggetto promise monitora le code fx; ma le code personalizzate possono essere specificate fornendo un parametro opzionale al metodo. Un oggetto con il metodo promise()
collegato, può essere specificato usando un secondo argomento opzionale per evitare di creare un nuovo oggetto.Il metodo può essere usato in congiunzione con metodi come
done()
per eseguire una funzione, quando tutte le animazioni su un elemento sono state completate:
#
$("#animated").promise().done(function () {
//fai qualcosa
});
#
Promise()
. Questo metodo restituisce un oggetto con quasi la stessa interfaccia che il Deferred, ma solo ha i metodi da attaccare ai callback e non ha i metodi per “resolve” e “reject“.Questo è utile quando si vuole fare una chiamata a una API per trarre uso, ma non ha la capacità di risolvere o rifiutare il differito. Questo codice non riuscirà perché la promise non ha un metodo “resolve“:
#
function getPromise(){
return jQuery.Deferred().promise();
}
try{
getPromise().resolve("a");
}
catch(err){
alert(err);
}
#
#
var post = jQuery.ajax({
url: "/echo/json/",
data: {
json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani"
})} ,
type: "POST"
});
post.done(function(p){
alert(p.firstName + " salvato.");
});
post.fail(function(){
alert("error!");
});
#
Seguendo l’esempio precedente, possiamo fare qualcosa del genere:
#
var post = jQuery.post(
"/echo/json/",
{json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani"
})
}).pipe(function(p){
return "Salvato " + p.firstName;
});
post.done(function(r){
alert(r);
})
#
Un’altra caratteristica molto interessante del metodo pipe è che può restituire un differito dall’interno del pipe di callback. Immaginiamo di avere due metodi, uno per ottenere un ID di un Cliente attraverso un id interno e un altro per ottenere l’indirizzo di una persona dal ID:
#
function getCustomerById(customerId){
return jQuery.post("/echo/json/", {
json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani",
ID: "123456789"
})
}).pipe(function(p){
return p.ID;
});
}
function getPersonAddressById(ID){
return jQuery.post("/echo/json/", {
json: JSON.stringify({
ID: "123456789",
address: "Sempre Viva 12345, Legione Straniera"
})
}).pipe(function(p){
return p.address;
});
}
function getPersonAddressById(id){
return getCustomerById(id)
.pipe(getPersonAddressById);
}
getPersonAddressById(123)
.done(function(a){
alert("L'indirizzo è " + a);
});
#
getPersonAddressByID
. Questo metodo restituisce una differita che è una combinazione dei due metodi. Se uno dei metodi della pipeline non supera il “master” differito allora fallirà.Le pipelines presentano alcuni altri usi, per esempio è possibile rifiutare la differita all’interno del callback del pipe.
Un altro caso interessante del uso che mi viene in mente per i pipes sono le differite ricorsive. Immagina che hai iniziato un’operazione asincrona nel backend, ed è necessario fare il “polling” per vedere se il compito è fatto e, quando il compito è fatto, fare qualcosa di diverso.
#
//1: fatto, 2: cancellato, other: attesa
function getPrintingStatus(){
var d = jQuery.Deferred();
jQuery.post(
"/echo/json/",
{
json: JSON.stringify({
status: Math.floor(Math.random()*8+1)
}),
delay: 2
}
).done(function(s){
d.resolve(s.status);
}).fail(d.reject);
return d.promise();
}
function pollUntilDone(){
//fare qualcosa
return getPrintingStatus()
.pipe(function(s){
if(s === 1 || s == 2) {
// se lo status è fatto o cancellato
// restituisce lo status
return s;
}
// se lo status è in attesa ...
// chiama la stessa funzione ...
// e restituisce un differito ...
return pollUntilDone();
});
}
jQuery.blockUI({message: "Caricando..."});
pollUntilDone()
.pipe(function(s){
// progetto il codice di stato
// in una stringa di senso compiuto.
switch(s){
case 1:
return "fatto";
case 2:
return "cancellato";
}
})
.done(function(s){
jQuery.unblockUI();
alert("Lo status è " + s);
});
#
jQuery.when
. Il metodo accetta un numero arbitrario di promesse, e restituisce un master differito che:
- sarà “resolved” quando tutte le promesse saranno risolte:
- sarà “rejected” se una delle promesse sarà rifiutata
La richiamata ha i risultati di tutte le promesse.
Questo è un esempio:
#
function getCustomer(customerId){
var d = jQuery.Deferred();
jQuery.post(
"/echo/json/",
{json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani",
ID: "123456789"})
}).done(function(p){
d.resolve(p);
}).fail(d.reject);
return d.promise();
}
function getPersonAddressById(ID){
return jQuery.post(
"/echo/json/",
{json: JSON.stringify({
ID: "123456789",
address: "Siempre Viva 12345, Legione Straniera"
})
}).pipe(function(p){
return p.address;
});
}
jQuery.when(getCustomer(123), getPersonAddressById("123456789"))
.done(function(person, address){
alert("Il nome è " + person.firstName + " e il suo indirizzo è " + address);
});
#
jQuery.when
restituisce una nuova differita e stiamo usando due risultati èer callback realizzato.
GetCustomer
; questo perché nella promessa di una chiamata AJAX, il carico utile è contenuto come primo elemento nel risultato, ma ha anche altre cose come il codice di stato.In questo esempio si possono mescolare jQuery.when e pipes come segue:
#
function getCustomer(customerId){
var d = jQuery.Deferred();
jQuery.post(
"/echo/json/",
{json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani",
ID: "123456789"
})
}).done(function(p){
d.resolve(p);
}).fail(d.reject);
return d.promise();
}
function getPersonAddressById(ID){
return jQuery.post(
"/echo/json/", {
json: JSON.stringify({
ID: "123456789",
address: "Siempre Viva 12345, Legione Straniera"
})
}).pipe(function(p){
return p.address;
});
}
jQuery.when(getCustomer(123), getPersonAddressById("123456789"))
.pipe(function(person, address){
return jQuery.extend(person, {address: address});
}).done(function(person){
alert("Il nome è " + person.firstName + " e il suo indirizzo è " + person.address);
});
#
jQuery.when
, è quando è necessario caricare parecchie cose sullo schermo, ma si vuole solo un unico messaggio per il loading:
#
function getCustomer(customerId){
var d = jQuery.Deferred();
jQuery.post(
"/echo/json/",
{json: JSON.stringify({
firstName: "Pippo",
lastName: "Cani",
ID: "123456789"
}),
delay: 4
}).done(function(p){
d.resolve(p);
}).fail(d.reject);
return d.c;
}
function getPersonAddressById(ID){
return jQuery.post(
"/echo/json/", {
json: JSON.stringify({
ID: "123456789",
address: "Siempre Viva 12345, Legione Straniera"
}),
delay: 2
}).pipe(function(p){
return p.address;
});
}
function load(){
jQuery.blockUI({message: "Caricando..."});
var loadingCustomer = getCustomer(123)
.done(function(c){
$("span#firstName").html(c.firstName)
});
var loadingAddress = getPersonAddressById("123456789")
.done(function(address){
$("span#address").html(address)
});
jQuery.when(loadingCustomer, loadingAddress)
.done(jQuery.unblockUI);
}
load();
#
Questo è tutto per ora! Spero che questo post sia utile, come lo è per me a portata di click.
Ancora nessun commento