Convertir un callback en promise

Pour rendre synchrone une fonction asynchrone avec await, le callback qui s'exécute quand elle se termine doit être changé en promise...

Pour illustrer comment cela peut se faire, on partira d'un exemple simple...

La fonction formula est exécutée sur un serveur, donc il faut un délai avant que le résultat n'arrive, on ajoute donc un callback pour les opérations qui dépendent de ce résultat.

function formula(a, b, callback) {
  var x = a * b
  callback(x)
}

On se sait pas quand la fonction formula a achevé son traitement (dans la pratique, le traitement est un peu plus compliqué). Si on veut, par exemple, afficher le résultat, on appelle alors la fonction ainsi:

formula(10, 5, function(x) {
  console.log(x)
})

Ce procédé à cependant quelques limitations. La première est que si le traitement qui utilise formula est lui aussi asynchrone, il faudra aussi le mettre dans une fonction avec callback, et on arrive souvent à un enchaînement de fonctions imbriquées l'une dans l'autre, dit: l'enfer des callbacks!

Un autre problème apparaît aussi quand les fonctions asynchrones sont celles d'une base de donnée (voir MySQL ou SQLite sur Node.js). Les opérations d'écriture sont asynchrones, les opérations de lecture sont asynchrone. Bien sûr, la lecture dépend de ce qui a été auparavant stocké dans la base. Mais la lecture ne vient pas en séquence après le stockage, les utilisateurs écrivent et lisent dans la base indépendamment les un des autres. Les callbacks ne sont pas utilisables.

Le callback est une solution limitée et inélégante, c'est pourquoi il convient de le remplacer par un procédé plus moderne: l'object Promise.

Utilisation avec then

La fonction formula sera réécrite ainsi:

function formula(a, b) {
  return new Promise(function(resolve, reject) {
    var x = a * b
    if(x == 0) reject("Error")
    else resolve(x)
  })
}

Promise à pour argument une fonction donts les arguments sont des fonctions qui s'exécutent, la première, resolve, quand le traitement est terminé avec succès, la seconde, reject, quand une erreur se produit. Resolve retourne le résultat du traitement, le mot-clé return est superflu ici.

L'appel aura maintenant cette forme:

formula(10,5).then(function(x) { console.log(x) })  

Utilisation avec async/await

On réutilise la fonction formula précédente, seul l'appel change:

async function test() {
   var x = await formula(10,5)
   console.log(x)
}

test()

L'appel doit se trouver à l'intérieur d'une fonction déclarée async, on ne peut utiliser await sans cela. Await attend l'achèvement de la fonction formula et rend alors le résultat disponible pour l'assigner à x.

Il est maintenant possible d'utiliser une fonction asynchrone sans se préoccuper des autres fonctions invoquées dans le traitement. On peut rêver de voir la bibliothèque libuv de Node.js réécrite entièrement sans callback!

Rendre synchrone une fonction avec callback

Dans le cas où une bibliothèque de fonction utilise des callbacks, comme c'est le cas de libuv, SQLite etc... on n'a pas le choix de la méthode, mais il est encore possible de rendre ces fonctions synchrones, en interfaçant une promise entre ces fonctions et l'appel.

Exemple d'interface à la fonction formula avec callback initiale:

function formula(a, b, callback) {
  var x = a * b
  callback(x)
}

function formulaSync(a, b) {
    return new Promise(function(resolve, reject) {
        formula(a, b, function(x) {
            if(x==0) reject(false)
            else  resolve(x)
        })
    })
}

async function test() {
    var x = await formulaSync(10, 5)
    console.log(x)
}

test()

Voir aussi:

© 8 septembre 2017 Xul.fr