L'asincronia sta ormai diventando una questione di tutti i giorni nelle applicazioni moderne così i linguaggi come C# si sono adeguati con costrutti come async/await. Javascript, pur essendo un linguaggio ormai antico secondo i tempi cui l'informatica ci ha abituato, gestisce da sempre molte attività in modo asincrono, utilizzando il meccanismo dei callback. Un esempio è il setInterval in cui uno degli argomenti passati è la funzione da chiamare ad intervalli regolari.

Librerie come jQuery e Angular stesso espongono numerosi casi di utilizzo di metodi asincroni. L'http service di Angular ad esempio è uno di questi casi. Esso però non utilizza normali callback ma un sistema molto simile al Task<T> di C#. Le promise. In effetti da ciascuna chiamata asincrona è ritornato un oggetto Promise che dispone dei metodi then, catch e finally. Questi servono per associare un callback a ciascuno delle condizioni che i nomi dei metodi evocano.

Ma la domanda cui vogli rispondere in questo post è la seguente: come faccio ad esporre una promise da un mio metodo asicrono se esso stesso no usa un servizio che la utilizzi? E' il caso ad esempio di un metodo che visualizzi una dialog modale. Essono mo potrà mai essere sincrono perchè oltre a risultare bloccante per il browser, non sarà comunque possibile gestire una eventuale "attesa" e ritornare dal metodo alla chiusure della dialog. Quello che ci viene in soccorso è il Q service. Esso è usato dallo stesso http service è ha come unico scopo la gestione asincrona. Vediamo un esempio:

   1: public showDialog(message: string, title: string = Constants.defaultModalTitle, commands?: IDialogCommand[]): ng.IPromise<string>
   2: {
   3:     var defer: ng.IDeferred<any> = this.$q.defer<string>();
   4:  
   5:     try
   6:     {
   7:         this.modalDialog.find('.modal-body').text(message);
   8:         this.modalDialog.find('.modal-title').text(title);
   9:  
  10:         var footer = this.modalDialog.find('.modal-footer');
  11:         footer.empty();
  12:  
  13:         for (var i in commands)
  14:         {
  15:             var cmd = commands[i];
  16:  
  17:             var button = $(Constants.modalButtonMarkup);
  18:  
  19:             button.text(cmd.text);
  20:             button.addClass(cmd.style == undefined ? 'btn-default' : cmd.style);
  21:             button.data('command-id', cmd.id);
  22:  
  23:  
  24:             button.click((ev) =>
  25:             {
  26:                 var id = $(event.srcElement).data('command-id');
  27:                 this.modalDialog.modal('hide');
  28:                 defer.resolve(id);
  29:             });
  30:  
  31:             button.appendTo(footer);
  32:         }
  33:  
  34:         this.modalDialog.modal('show');
  35:     }
  36:     catch (error)
  37:     {
  38:         defer.reject(error);
  39:     }
  40:  
  41:     return defer.promise;
  42: }

Diamo per assodato che questo metodo faccia capo ad una classe che ha ricevuto una istanza del Q service come argomento del costruttore. Come segue:

   1: constructor(private $q: ng.IQService)
   2: {
   3: }

Il metodo showDialog al proprio inizio crea una istanza di una classe di tipo IDeferred<any>, dove any è il tipo del valore ritornato dal metodo in questione. L'oggetto deferred è quello che ci serve per gestire l'asincronia. Al termine del metodo infatti viene ritornata la promise che esso contiene con "defer.promise".

A questo punto è necessario invocare i callback di successo (then) e quello di fallimento (catch). Per farlo sono utilizzati i metodi "resolve" e "reject" dell'oggetto deferred.

Così facendo è possibile usare il metodo come segue:

   1: this.showDialog(
   2:     'Are you sure you want to delete the item',
   3:     'Confirm operation',
   4:     sys.Constants.modalYesNo).then(
   5:         (id: string) =>
   6:         {
   7:             if (id == sys.Constants.Positive)
   8:             {
   9:                 // delete the item here
  10:             }
  11:         });

Il sistema è l'unico consigliato. Infatti l'utilizzo dei callback normali crea problemi al sistema di databinding di AngularJS. Invece il q service è perfettamente in grado di supportarlo.