author
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 passer d’une écriture de code à base de callback à 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:
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);
});
}
});
};
Puis en CoffeeScript:
importConferences = (conferences, callback) ->
conferenceImporter.importData conferences, (err, result) ->
if err
callback(err)
else
doSomethingWithResult result, (err, otherResult) ->
callback(err, otherResult)
L’usage de CoffeeScript permet déjà de gommer une partie 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 s’y 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:
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
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:
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}"
En appliquant également la fonction defer() pour réécrire les fonctions importData et doSomethingWithResult, nous obtenons le code suivant:
importConferences = (conferences) ->
conferenceImporter.importData(conferences)
.then (result) ->
doSomethingWithResult(result)
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:
importConferences = (conferences) ->
deferred = Q.defer()
conferenceImporter.importData conferences, (err, result) ->
doSomethingWithResult result, deferred.makeNodeResolver()
deferred.promise
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: