Il y a 9 ans -
Temps de lecture 3 minutes
Transformez vos callbacks Node.js en promises Q
Le callback Hell en JavaScript, on en a tous entendu parler, voir même un peu trop, mais pas sans raison.
De bonnes règles de codage permettent tout de même de gommer en grande partie ce problème et l’usage des promises est à mettre en tête de liste des bonnes pratiques pour y parvenir.
Nous allons voir dans cet article différentes techniques proposées par le module Q permettant de passer d’une écriture de code à base de callbacks à une écriture à base de promises.
Un exemple de départ
Partons de l’extrait suivant qui permet d’importer des données de conférences:
En JavaScript :
[javascript gutter= »true »]var importConferences = function(conferences, callback) {
return conferenceImporter.importData(conferences, function(err, result) {
if (err) {
return callback(err);
} else {
return doSomethingWithResult(result, function(err, otherResult) {
return callback(err, otherResult);
});
}
});
};[/javascript]
Puis en CoffeeScript :
[javascript gutter= »true »]importConferences = (conferences, callback) ->
conferenceImporter.importData conferences, (err, result) ->
if err
callback(err)
else
doSomethingWithResult result, (err, otherResult) ->
callback(err, otherResult)[/javascript]
L’usage de CoffeeScript permet déjà de faire disparaître une partie de l’effet pyramide des appels de fonctions. Néanmoins, ce n’est pas suffisant, et dans le cas où de nombreux appels sont imbriqués, on se perd rapidement dans la compréhension du programme.
Q.defer()
Avec l’usage de la fonction defer() de Q il sera possible de réécrire le même code de la façon suivante :
[javascript gutter= »true »]importConferences = (conferences) ->
deferred = Q.defer()
conferenceImporter.importData conferences, (err, result) ->
doSomethingWithResult result, (err, result) ->
if err
deferred.reject(err)
else
deferred.resolve(result)
deferred.promise[/javascript]
C’est un peu plus verbeux, mais la transformation du callback initial en promise permet maintenant d’appeler la fonction importConferences de la façon suivante :
[javascript gutter= »true »]importConferences(conferences)
.then (result) ->
console.log "Result: #{util.inspect(result)}"
someOtherPromise()
.then (otherResult)
console.log "Result: #{util.inspect(otherResult)}"
.fail (err) ->
console.log "Error – Message: #{err.message}"[/javascript]
En appliquant également la fonction defer() pour réécrire les fonctions importData et doSomethingWithResult, nous obtenons le code suivant :
[javascript]importConferences = (conferences) ->
conferenceImporter.importData(conferences)
.then (result) ->
doSomethingWithResult(result)[/javascript]
Plus besoin de la fonction defer() puisque nous travaillons avec des fonctions retournant des promises.
La fonction fail() n’est pas utilisée ici, car nous laissons le soin à la fonction appelante d’ajouter la gestion des erreurs à la promise retournée.
deferred.makeNodeResolver()
Dans certains contextes, vous ne pouvez pas vous baser sur une promise retournée par un appel de fonction. C’est par exemple le cas lorsque vous appelez une fonction d’un module qui ne supporte pas les promises.
Pour éviter d’écrire une résolution manuelle de promise basée sur le résultat d’un callback, Q propose la fonction makeNodeResolver. Elle simplifie l’écriture de conversion comme suit :
[javascript gutter= »true »]importConferences = (conferences) ->
deferred = Q.defer()
conferenceImporter.importData conferences, (err, result) ->
doSomethingWithResult result, deferred.makeNodeResolver()
deferred.promise[/javascript]
Le code n’est finalement pas plus verbeux que l’original et vous permet de travailler avec des promises plutôt que des callbacks.
Conclusion
Les techniques de conversion visant à remplacer l’usage de callback par des promises sont variées et permettent en général de s’adapter au besoin.
Liens utiles
Pour en savoir plus sur la librairie Q, vous pouvez visiter la page du projet sur GitHub à l’adresse suivante:
Commentaire
3 réponses pour " Transformez vos callbacks Node.js en promises Q "
Published by Nicolas Demengel , Il y a 9 ans
Merci pour cet article Alexis !
J’ajouterais à ta liste Q.denodeify (aka Q.nfbind, voir ici : https://github.com/kriskowal/q/wiki/API-Reference#qnfbindnodefunc-args) qui permet de décorer les fonctions de node de manière à retourner des promesses. Sur ton exemple :
var doSomething = Q.denodeify(doSomethingWithResult);
doSomething(result).then(…);
La gêne peut être réduite en créant un module promises.js exposant les versions « promessifiées » des fonctions node les plus couramment utilisées. On peut ainsi écrire :
var readFile = require(‘./promises’).fs.readFile; // instead of require(‘fs’).readFile;
readFile(/* … */).then(/* … */);
Aussi, une variante au combo « Q.defer()/deferred.resolve/deferred.reject/return deferred.promise » consiste à écrire « return Q.Promise(function (resolve, reject) { /* code calling resolve or reject when done */ }) ». C’est clairement affaire de gout, la verbosité étant équivalente.
Dans tous les cas, je dirais que ça reste une gymnastique malheureuse, et même la possibilité à venir d’utiliser des générateurs (https://github.com/kriskowal/q/wiki/API-Reference#generators) génère du bruit qui nous fait perdre en expressivité.
Published by jb cazaux , Il y a 8 ans
Hello, je me permet de rajouter un pointeur vers mon article sur le même thème (mais sans coffee): https://github.com/jbcazaux/nodeQ
Published by Koren , Il y a 7 ans
Hello,
Pour info, defer() est déprécié, car considéré comme un anti-pattern.
https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred