Negli ultimi anni JavaScript è stato protagonista dei mutamenti che hanno portato all'affermazione del Web 2.0 e alla diffusione delle Rich Internet Application. L'adozione di logiche di funzionamento lato client hanno trasformato il browser in un'applicazione in grado di gestire ogni tipo di contenuto. L'usabilità dell'interfaccia è uno dei fattori che ha permesso a chiunque di scrivere, postare, pubblicare e condividere. D'altro canto, questa rivoluzione ha visto tramontare il vecchio modo di lavorare con JavaScript. La diffusione di framework e librerie di alto livello, come jQuery, Prototype e MooTools hanno in parte sostituito le API classiche di JavaScript, imponendosi come interfaccia tra lo sviluppatore e il browser. Ciò non significa che siamo autorizzati a dimenticare i principi di funzionamento di JavaScript: librerie come jQuery, ProtoType ecc. sono realizzate in JavaScript e non possono stravolgerne l'architettura. I meccanismi di base di JavaScript sono le fondamenta (e spesso anche il pian terreno) su cui sono edificate le API di livello superiore.

Un esempio ricorrente è la gestione delle chiamate asincrone. Oggi vedremo un esempio concreto in jQuery, che ci servirà a discutere un errore molto frequente, dovuto proprio alla mancata comprensione di uno dei principi fondamentali di JavaScript.

Prima di iniziare, ripassiamo velocemente le differenze tra codice sincrono e codice asincrono.

Un pezzo di codice sincrono viene eseguito in “serie”, ovvero in modalità “catena di montaggio”. Ad esempio, se l'utente clicca su un pulsante attivando una funzione sincrona, il browser resterà bloccato fino al termine della funzione. Viceversa, se usiamo del codice asincrono, subito dopo aver cliccato il pulsante l'utente avrà di nuovo il controllo del browser. L'esecuzione di una chiamata asincrona avviene in “parallelo”, ovvero segue una strada diverso rispetto a quella principale, che rimane in ascolto delle azioni dell'utente. In termini più rigorosi ciò significa che nel browser non abbiamo la possibilità di scrivere codice multi-threading: il codice JavaScript viene eseguito con un unico thread, per cui l'unico modo di eseguire azioni (apparentemente) simultanee è proprio il meccanismo di chiamata asincrona.

Un esempio pratico dovrebbe chiarire quanto detto. Consideriamo una pagina HTML che contenga il seguente script

function getData() {

var data ;

$.get("data.xml", function(returned) {

data = returned.childNodes[0].textContent;

console.log("step 1") ;

}) ;

onsole.log("step 2") ;

return data ;

}

var a = 100 ;

var b = getData() ;

console.log(a + eval(b)) ;

Per semplicità nell'esempio qui sopra abbiamo ipotizzato di recuperare un valore numerico dal file data.xml, che potrebbe contenere qualcosa del genere

<param>42</param>

ma sarebbe lo stesso se la chiamata get fosse indirizzata verso una pagina remota. Abbiamo scelto di leggere i dati da un file per non mettere troppa carne sul fuoco, in modo da concentrarci sulla differenza tra codice sincrono e codice asincrono.

Il codice qui sopra gira così com'è su Firefox: se usiamo un altro browser probabilmente non avremo a disposizione l'oggetto console, ma basta sostituire le istruzioni console.log con i soliti alert e tutto dovrebbe andare a posto.

Se eseguiamo il codice troveremo un risultato del tipo

step 2

NaN

step 1

che suggerisce dov'è l'errore: quando arriviamo a chiamare la funzione getData, il thread di esecuzione arriva fino alla $.get() di jQuery, che per default è una chiamata asincrona. A questo punto il codice rimane “in attesa”, e l'esecuzione torna alla riga successiva alla chiamata, cioè all'istruzione console.log(“step 2”). Ecco perché il codice passa prima per lo step 2, e solo dopo per lo step 1: la funzione getData viene completata anche se la sua funzione “interna” (i.e. la $.get) rimane in attesa di ricevere i dati. Per questo motivo, quando cerchiamo di stampare la somma a + b otteniamo come risultato NaN, cioè Not a Number. Ciò è corretto, perché al momento di calcolare la somma JavaScript non conosce il risultato che verrà attribuito alla variabile b.

Il codice corretto è il seguente

function getData(handleData) {

var data ;

$.get("data.xml", function(returned) {

handleData(returned.childNodes[0].textContent) ;

}) ;

return data ;

}

var a = 100 ;

getData(function(data) {

console.log(a + eval(data)) ;

}) ;

in questo caso, al momento di eseguire la getData, passiamo come argomento una funzione anonima, che si occupa semplicemente di trattare i dati, loggando la somma della variabile a con il risultato della chiamata che verrà eseguita all'interno della getData.

Se non siamo abituati a lavorare in questo modo il codice sembrerà un po' “tortuoso” o addirittura “magico”, ma è solo questione di leggerlo con un po' di pazienza, aiutandosi con carta e penna.

Quello che potrebbe confondere, soprattutto all'inizio, è il gioco di scambio dei ruoli tra i vari attori: abbiamo funzioni che accettano argomenti, i quali a loro volta sono funzioni, che a loro volta accettano in ingresso parametri che verranno definiti altrove. Questo meccanismo estende il principio di funzionamento dell'oggetto JavaScript sottostante, che è il caro vecchio XMLHttpRequest: se l'argomento continua a sfuggirci, potrebbe valer la pena rispolverare le conoscenze di base di JavaScript, come accennato all'inizio dell'articolo. Oppure, se ci basta che il codice “funzioni”, possiamo usare l'esempio qui sopra come template e copiarlo dove ci serve: in molti casi questo è l'approccio più adatto, e a volte è il modo migliore di chiarirsi le idee.