Esistono diverse motivazioni per le quali una richiesta asincrona possa apparentemente svanire nel nulla. Tra le più frequenti troviamo i rallentamenti sulla rete e il sovraccarico del server, che potrebbe lasciare il navigatore in attesa di una risposta ma non essere in grado di elaborarla, se non entro tempi eccessivi.
L'utente in questi casi accusa un disservizio, che quando persistente, crea perplessità sulla professionalità dello sviluppatore del sito.
Quando il server non può rispondere ad una richiesta risponde con uno stato diverso da 200 (ok). Ma in caso di latenze questo non accade. È consigliabile quindi controllare il lasso di tempo intercorso tra l'azione richiesta da parte dell'utente e la reale possibilità di soddisfare il suo intento, specie qualora il server avesse frequenti problemi di banda o di velocità.
La reazione tipica dell'utente che non riceve risposta da una pagina è quella di tentare di ricaricarla ripetutamente. Questo comportamento sarebbe da evitare in quanto aggiunge richieste ad un server già saturo, che non riuscirà a smaltire il suo carico, continuando a lasciare l'utente in attesa di una risposta.
Le situazioni descritte possono essere affrontate in diversi modi, ne esaminiamo due tra più semplici.
Gestione di un time-out
Il meccanismo più importante che stiamo per mettere in piedi è una temporizzazione. Decidiamo di considerare la richiesta fallita dopo un certo tempo di attesa senza risposte ed in caso di fallimento avvisiamo l'utente.
La prima modifica di rilievo alla funzione utilizzata nella richiesta XML, ma riproponibile anche in quella testuale è il tipo di dato in ingresso. Non passiamo più la stringa contenente la url del documento da leggere ma il riferimento all'intero link. Modifichiamo quindi il comportamento sull'evento onclick scrivendo return caricaXML(this);
al posto di return caricaXML(this.href);
.
I commenti alla funzione caricaXML()
sono molto utili a capire i passaggi e le rifiniture possibili di questo meccanismo. Facciamo caso in particolare ad un controllo sul link per evitare le richieste (click) ripetute, che può tornarci utile in molte situazioni.
Listato 21. La funzione caricaXML() con time-out
function caricaXML(link) {
var
ajax = assegnaXMLHttpRequest(),
elemento = prendiElementoDaId("contenuto-dinamico"),
usaLink = true,
// variabili di controllo del tempo
// data di inizio interazione
dataChiamata = new Date(),
// tempo in millisecondi dell'inizio
inizioChiamata = dataChiamata.getTime(),
// secondi di attesa prima di fermare l'interazione
massimaAttesa = 5,
// variabile cui assegnare la funzione di verifica
verificaTempoTrascorso;
if(ajax) {
usaLink = false;
// parametro fittizio all'elemento link
// per evitare un doppio click
if(!link.clicked) {
// se il parametro è risultato undefined o false
// entriamo e gli assegnamo true
link.clicked = true;
// NOTA. Qualora l'elemento dovesse essere parte di un form si potrebbe
// sfruttare il parametro "disabled", assegnandolo solo quando necessario.
// effettuiamo la chiamata sul parametro href
ajax.open("get", link.href, true);
ajax.setRequestHeader("connection", "close");
ajax.onreadystatechange = function() {
// se la richiesta è già stata completata
if(ajax.readyState === readyState.COMPLETATO) {
// annulliamo la funzione di verifica tempo
verificaTempoTrascorso = function(){};
// e continuiamo con i normali controlli
if(statusText[ajax.status] === "OK") {
if(ajax.responseXML)
elemento.innerHTML = parsaXml(ajax.responseXML);
else
elemento.innerHTML =
"L'XML restituito dalla richiesta non è valido.<br />" +
ajax.responseText.split('<').join("<").split('>').join(">");
}
else
elemento.innerHTML =
"Impossibile effettuare l'operazione richiesta.<br />" +
"Errore riscontrato: " + statusText[ajax.status];
// se il link non dovesse esser sovrascritto, come invece accade in questo esempio,
// è importante ricordarsi di riabilitarlo reimpostando il parametro fittizio
// Esempio: link.clicked = false;
}
// se la richiesta non è stata ancora ultimata
// è possibile sfruttare la variabile massimaAttesa
// per verificare se il controllo sul tempo trascorso
// sia stato creato o meno.
// Essendo tale valore un intero rappresentante i secondi
// ma non essendo ancora stato riassegnato nel corrispettivo
// in millesimi, questo else if può garantire che
// il codice al suo interno verrà eseguito una sola volta
else if(massimaAttesa < 1000) {
// conversione di massimaAttesain millisecondi
massimaAttesa = massimaAttesa * 1000;
// il controllo sul tempo trascorso deve essere
// asincrono a questa funzione poichè non è detto
// che il cambio di stato della richiesta
// venga effettuato in tempi utili.
// Una funzione apposita per la verifica
// è la soluzione più indicata
verificaTempoTrascorso = function() {
// ogni chiamata asincrona a questa funzione
// dovrà verificare la durata dell'interazione
// è necessario quindi ridichiarare la variabile
// al fine di ottenere il nuovo oggetto Date
dataChiamata = new Date();
// Se il tempo trascorso è maggiore della
// massima attesa ...
if((dataChiamata.getTime() - inizioChiamata) > massimaAttesa) {
// ... interrompiamo la richiesta ed
// informarmiamo l'utente di quanto avvenuto.
// Quindi riassegnamo onreadystatechange ad una
// funzione vuota, poichè quest'evento sarà
// sollevato chiamando il metodo abort()
ajax.onreadystatechange = function(){return;};
// è possibile a questo punto richiamare il metodo abort
// ed annullare le operazioni dell'oggetto XMLHttpRequest
ajax.abort();
// creiamo un elemento per avvertire l'utente
// del fallimento della richiesta da aggiungere
// a quello predisposto per mostrare il risultato.
// Usiamo il metodo createElement() del document e
// non innerHTML,che potrebbe riscrivere il link selezionato
// annullando l'assegnazione del parametro fittizio.
// Avendo annullato l'utilità dell'oggetto XMLHttpRequest
// è possibile anche riciclare la variabile 'ajax'.
ajax = document.createElement("p");
ajax.innerHTML =
"Spiacente, richiesta fallita.<br />" +
"La prego di ritentare tra qualche istante.";
elemento.appendChild(ajax);
// è possibile ripristinare il parametro fittizio del link
// su 'false' sia subito che con qualche istante di latenza,
// Infatti l'attuale situazione può essere generata dalla
// risposta lenta di un server sovraccarico e qualche attimo
// di tranquillità in più non guasta.
// Tuttavia per semplificare il codice ripristiniamo il link qui.
link.clicked = false;
}
// se invece il tempo è inferiore al timeout
else
// si richiama questa stessa funzione, con un tempo
// che non dovrà essere ne alto ne troppo basso.
setTimeout(verificaTempoTrascorso, 100);
};
// definita la funzione non resta che avviarla
verificaTempoTrascorso();
};
};
ajax.send(null);
};
};
return usaLink;
};
Quanto mostrato è utile soprattutto in situazioni critiche e potrebbe non esseresempre indispensabile ma il controllo sul link, che permette di filtrare le richieste, può essere la maggiore fonte di stabilità o velocità di una pagina potenzialmente ricca di accessi simultanei che con questo semplice accorgimento, non sovraccaricheranno il server con richieste multiple ed identiche.