Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Javascript asincrono con JSDeferred

Introduzione a JSDeferred, libreria standalone per la gestione di callback asincrone
Introduzione a JSDeferred, libreria standalone per la gestione di callback asincrone
Link copiato negli appunti

JSDeferred è una libreria che viene dal sol levante (scritta da Hirofumi Watanabe) e che permette di lavorare con le callback asincrone in modo potente, ordinato e semplice. Introduce una sintassi che ci permette di creare e governare costrutti asincroni di una certa complessità. Per capire meglio di cosa si tratta sarà utile qualche esempio.

In questo articolo, senza la pretesa di esaurire l'argomento, esploreremo quindi rapidamente alcune caratteristiche di questa libreria con esempi pratici tipici della programmazione Web.

JSDeferred non dipende da librerie esterne, quindi il il caricamento di jsdeferred.js è sufficiente per usarlo. Ecco come aggiungere la libreria nell'head:

<script type="text/javascript" src="jsdeferred.js"></script>
<script type="text/javascript" src="utilita.js"></script>

Subito dopo la libreria abbiamo caricato il file utilita.js che conterrà il codice che scriveremo.

Riconoscimento utenti

Come primo caso d'uso prendiamo quello di dell'ottenimento di un profilo di un utente e dei suoi nuovi messaggi ricevuti da visualizzare in una pagina:

Una implementazione sincrona sarebbe:

var profilo = get_profile(utente); // e aspetto
var msg = get_msgs(utente);        // e aspetto
render_page({'profilo' : profilo, 'msg' : msg});

Con gli oggetti profilo e msg vengono popolati in sincrono, cosa abbastanza naturale visto che la sequenza delle azioni prevede un ordine. Il difetto di questa implementazione è quello di bloccare qulunque altra cosa, naturalmente.

La versione asincrona del nostro esempio sarà:

var parametri = {};
get_profile(utente, function(profilo){
    parametri['profilo'] = profilo;
    get_msgs(utente, function(msg){
             parametri['msg'] = msgs;
             render_page(parametri);
    })
})

Ora abbiamo una soluzione con tante callback annidate, che si attivano in modo asincrono garantendo il rispetto della sequenza delle azioni.

Il punto qui è che scrivere codice in questo modo può risultare particolarmente scomodo per la lettura e la manutenzione, basta immaginare di voler implementare sequenze molto lunghe di azioni: si andrebbe incontro ad un annidamento molto complesso.

JSDeferred ha l'obiettivo di concatenare le azioni in modo molto più intuitivo. Vediamo il codice trasformato:

var parametri = {};
next(function(){
    return get_profile(utente)
}).
next(function(profilo){
    parametri['profilo'] = profilo
}).
next(function(){
    return get_msgs(utente)
}).
next(function(msg){
    parametri['msg'] = msg;
    render_page(parametri);
})

Risulta certamente più facile da leggere e gestire. Il metodo next() fondamentalmente istanzia un nuovo oggetto JSDeferred e prende in input la definizione della funzione.

Oltre a next() abbiamo numerose altre funzioni. Noi focalizzaremo l'attenzione su alcuni oggetti che hanno maggior impatto durante la programmazione:

  • loop(), fornisce un ciclo "asincrono". Questo ciclo però; è abbastanza lento, ma permetterà alla pagina di non bloccarsi
  • call(), è il responsabile delle callback asincrone
  • parallel(), che vedremo più; avanti
  • wait(), che semplicemente blocca l'esecuzione per un tempo stabilito

Continuando con il nostro esempio semplici, consideriamo di voler ottenere i dati di più utenti in parallelo e accelerare al massimo la visualizzazione della pagina. Solitamente viene utlizzata una struttura simile:

var parametri = {
	'profilo' : null,
	'msg' : null
};
function render_page() {
    if (parametri['profilo'] && parametri['msg'] !== null){
         do_render_page();
    }
}
get_profile(utente, function(data){
    parametri['profilo'] = data;
    render_page();
});
get_msgs(utente, function(data){
    parametri['msg'] = data;
    render_page();
});

Utilizzando la nostra libreria invece, la soluzione sarebbe:

var process = render_page.after('profilo', 'new_msg');
get_profile(utente, process.profilo);
get_msgs(utente, process.new_msg );

Il collegamento successivo viene richiamato solo dopo che tutte le richieste in parallelo restituiscono i risultati. Il metodo parallel è utile quando alcune risorse vengono richieste in asincrono. L'oggetto ritornato da questo metodo può; essere un oggetto o un array di oggetti, nel nostro esempio l'oggetto parametri.

Se vengono specificati più; oggetti come argomenti, questi vengono inseriti in un array.

parallel([
    get_profile(utente),
    get_msgs(utente)
]).
next(function(parametri){
    render_page({'profilo' : parametri[0], 'msg' : parametri[1]})
    //Potrei scrivere anche così
    //render_page.apply(this, parametri);
})

Gestione delle eccezioni con JSDeferred

Affrontiamo ora un aspetto fondamentale della programmazione, la gestione degli errori, o delle eccezioni, generate nel codice. Vediamo cosa succede ad esempio durante l'uso del metodo get_profile() o get_msgs(). Per la programmazione sincrona il codice sarebbe questo:

try{
    var msg = get_msgs(utente);
    var profilo = get_profile(utente);
    if (profilo){
         render_page({'profilo' : profilo, 'msg' : msg});
    }else{
         redirect_to_login_page();
    }
}catch(e){
    show_error_msg(e);
}

Il classico try/catch. Grazie a questa libreria possiamo passare gli errori come parametro nella callback. Essi saranno poi trasferiti ad una funzione generalizzata che notifica all'utente l'errore.

Se c'è una cosa di veramente buono in JSDeferred, è proprio la gestione degli errori.

Browser come Firefox, solitamente bloccano i processi asincroni se vanno in errore, e questo spesso si traduce in un blocco del rendering della pagina. Ci ritroviamo quindi con l'intestazione della pagina ma senza il corpo del documento, dando all'utente una sensazione di instabilità del sistema.

JSDeferred è in grado di creare quello che viene definito un error-back. Se dopo una funzione viene sollevato un errore, non viene impedita la normale prosecuzione della catena di funzioni, viene semplicemente resituito un valore normale.

Nel nostro caso, se get_profile generasse un erorre, la visualizzazione non si bloccherebbe, verrebbe semplicemente redirezionata alla pagina di login. Vediamo la sua applicazione:

var parametri = {};
next(function(){
    return get_profile(utente)
}).
error(function(e){
    redirect_to_login_page();
}).
next(function(profilo){
    parametri['profile'] = profilo
}).
next(function(){
    return get_msgs(utente)
}).
error(function(e){
    show_error_msg(e);
}).
next(function(msg){
    parametri['msg'] = msg;
    render_page(parametri);
})

Aggiungendo semplicemente il metodo error() catturiamo tutte le eccezioni che si verificano e non impediamo la prosecuzione dello script.

Continuando con i nostri esempi, vediamo come potremmo ottenere un elenco di commenti recenti, leggere gli ultimi argomenti e un rating sui commenti stessi, con frequenza sempre più; alta, sempre in base al nostro profilo. L'esempio precedente mostrato si trasformerebbe in un groviglio ingiustificabile.

var parametri = {};
get_profile(utente, function(profilo){
    parametri['profilo'] = profilo;
    get_msgs(utente, function(msg){
         parametri['msg'] = msg;
         get_last_comments(utente, function(commenti){
              parametri['commenti'] = commenti;
              get_last_readed_topics(utente, function(topic){
                   parametri['topic'] = topic;
                   get_rating(utente, function(rating){
                        parametri['rating'] = rating;
                        render_page(parametri)
                   })
              })
         })
    })
})

Immaginiamo cosa diventerebbe se aggiungessimoanche una callback per la gestione degli errori...

Per risolvere questo groviglio con JSDeferred, basta aggiungere un nuovo elemento alla catena vista precedentemente. Tutto qui.

Ajax con JSDeferred

Come ultimo esempio sfruttiamo XMLHttpRequest, definiamo un http.get che non abbiamo mai usato in precedenza.

http = {}
http.get = function (uri) {
	var deferred = new Deferred();
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function () {
		if (xhr.readyState == 4) {
			if (xhr.status == 200) {
				deferred.call(xhr);
			} else {
				deferred.fail(xhr);
			}
		}
	};
	deferred.canceller = function () { xhr.abort() };
	return deferred;
}

Abbiamo creato un'istanza differita con new Deferred(), e invocato il suo call() all'interno di un metodo di callback asincrono. L'istanza dell'oggetto JSDeferred sarà associata ed eseguita nella catena di callback del Deferred stesso.

Allo stesso modo, all'invocazione del metodo fail() viene generato un errore. Se vogliamo intercettare l'eccezione usando error() come prima, è necessario chiamare il fail() nel modo corretto.

Abbiamo anche definito canceller, viene eseguito quando è invocato il metodo cancel() dell'istanza Deferred. Forse normalmente non lo utilizzeremmo, ma è giusto ricordare che esiste.

Per le singole richieste AJAX utilizzare oggetti Deferred può diventare complesso e discutibile, ma se vengono utilizzate molte chiamate asincrone correlate, o se c'è in prospettiva che il progetto cresca e diventi sempre più; difficile, allora ha senso considerare l'utilizzo di oggetti Deferred. Questo è un meccanismo molto potente con la quale sviluppare una catena di grandi dimensioni con callback parallele/seriali e la gestione degli errori, e renderebbe il codice più ordinato e gestibile.

Link Utili

Ti consigliamo anche