Du Q ! Promis !

L'une des grandes difficultés lors de la découverte de Javascript, surtout coté serveur, est l'enchainement de fonctions asynchrone où chaque callback transforme rapidement le code en une pyramide illisible. C'est contre ça que se bat le concept des promises qui nous permet d'avoir la même souplesse que la programmation par exception que propose les langages procéduraux. Prenons un exemple classique où l'on lit 3 fichiers les uns après les autres.

Callbacks unchained

var fs = require('fs'),  
    content = '';

fs.readFile('monfichier1.txt', function(err, data) {  
  if (err) {
    console.error(err);
    return;
  }
  content += data.toString();

  fs.readFile('monfichier2.txt', function(err, data) {
    if (err) {
      console.error(err);
      return;
    }

    content += data.toString();

    fs.readFile('monfichier3.txt', function(err, data) {
      if (err) {
        console.error(err);
        return;
      }

      content += data.toString();

      console.log(content);
    });
  });
});

Et c'est un exemple très simple. En programmation procédurale, on aurait quelque chose de ce genre :

content = '';  
try {  
  content += readFile('monfichier1.txt');
  content += readFile('monfichier2.txt');
  content += readFile('monfichier3.txt');
  log(content);
} catch (Exception) {
  log(Exception);
}

Ce qui est clairement plus lisible.

Et bien grâce aux promises, c'est possible, je vous le promet !

Q

Nous allons utiliser Q qui a l'avantage d'être disponible coté serveur et coté client et même d'être intégré à Angular sous la forme $q. Nous aurons au final le code suivant :

var readFile = require('./readFile'),  
    content = '';

readFile('monfichier1.txt')  
  .then(function(data) {
    content += data.toString();
    return readFile('monfichier2.txt');
  })
  .then(function(data) {
    content += data.toString();
    return readFile('monfichier3.txt');
  })
  .then(function(data) {
    content += data.toString();
    log(content);
  })
  .catch(function(err) {
    console.error(err);
  });

Et pour comprendre la magie des promises et de Q, voici le code de la fonction readFile :

var fs = require('fs'),  
    Q = require('q');

function readFile(path) {  
  var deferred = Q.defer();
    fs.readFile(path, function(err, data) {
      if (err) {
        deferred.reject(err);
      }
      deferred.resolve(data);
    });
    return deferred.promise;
};

À noter que Q s'installe simplement avec npm d'un coup de :

npm install q  

Alors que se passe-t-il dans ce script ? Nous créons d'abord un différé avec Q.defer(), nous appellons notre méthode asynchrone et nous retournons immédiatement la promesse de notre différé : deferred.promise. Pour le moment, notre promesse est en attente de résolution. Puis la méthode asynchrone éxecute le callback. Si une erreur survient alors le différé est rejeté via deferred.reject(), sinon, il est résolu grâce à deferred.resolve(). Dans les deux cas, un seul paramètre est envoyé. Dans le cas où le différé est rejeté, alors c'est la méthode catch() qui sera exécutée. Dans le cas où elle est résolue, c'est la méthode then().

La méthode then() est chainable. C'est pourquoi dans l'exemple ci dessus, le premier then() retourne une promesse qu'on peut alors enchainer directement d'un autre then(). Le dernier catch() sera éxecuté quelque soit le then() qui aura été rejeté.

Adoptez de suite les promises !

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu