Node, functions asynchones et async

Ces derniers temps, je fais beaucoup de javascript… vraiment beaucoup…. et j’aime ça!

Il y a un « shift » de mentalité à faire lorsqu’on n’est pas habitué à utiliser des callbacks tous les jours… Mais en en faisant plus, on se rend compte de la puissance de la chose. Et j’aime ça! Mes vieux réflexes sont parfois encore là mais « je me soigne », et ça va de mieux en mieux.

J’ai eu un problème dernièrement, où il fallait que plusieurs callbacks de fonctions soient executés avant de passer à la suite.

Veuillez noter dans les exemples qui suivent, je vais utiliser la fonction setTimeout pour illustrer le code, mais vous pouvez remplacer setTimeout par toute autre function qui accepte un callback comme paramètre. En utilisant setTimeout, on ne se rend pas bien compte qu’on recherche à recevoir un résultat (en paramètre du callback), mais bon…

Une première approche, qui fonctionne, est la suivante:

setTimeout(function(
    // with "real functions with callbacks, 
    // the "result" is passed as params of the callback function
    ) {
    console.log('in function 1 callback');
    // ok, now run function2
        setTimeout(function() {
        console.log('in function 2 callback');
        // ok, I'm done
        // now, do what you have to do...
        // ...
    }, 100);
}, 200);

console.log('the end!');

Le résultat:

the end!
in function 1 callback
in function 2 callback

Cela fonctionne, mais on voit vite le problème: si on a une 3ème fonction à exécuter, on rajoute un autre niveau… De plus en plus difficile à lire, pas super élégant (à mon goût)… Donc plus d’erreurs en cas de modification future.
En plus, une telle approche exécute les differentes fonctions en série, les unes après les autres.

Donc, après une rapide recherche, j’ai découvert la librairie async de caolan (que je ne connaissais pas, mais qui est probablement connu de tout développeur nodejs (?)), initialement développé pour nodejs mais également disponible dans le browser. Cette librairie semble très populaire. Elle est notamment dans le top 6 des packages npm dont dépendent le plus d’autres packages npm.

async contient de nombreuses fonctions qui permettent de resoudre le genre de problèmes décrits plus haut.
Voici un exemple d’utilisation de aync.parallel:

// this example uses nodejs module loading system
var async = require('async');

async.parallel([
    function(callback){
    	// any function with callback
        setTimeout(function(){
        	console.log('in function 1 callback');
        	// when you have what you want, call "callback"
        	// to notify async that the function 1 is done
        	// note: we are in the setTimeout callback function,
        	// that is the beauty!!
            callback(null, 'function 1 result');
        }, 200);
    },
    function(callback){
        setTimeout(function(){
            console.log('in function 2 callback');
            callback(null, 'function 2 result');
        }, 100);
    },
],
// optional callback
function(err, results){
	// we are here only if function 1 and 2 called callback
	// or if one callback param 1 (err) is not null
    console.log('test 1 - in final callback - err:', err, ' - results: ', results);
});

console.log('the end!');

Le résultat (exécuté en ligne de commande « node test-async.js »):

the end!
in function 2 callback
in function 1 callback
test 1 - in final callback - err: undefined  - results:  [ 'function 1 result', 'function 2 result' ]

C’est certain que le code est plus long (mais j’ai aussi mis plus de commentaires…), mais on a des avantages:

  • les functions sont exécutées en parallèle. Veuillez noter qu’il y a de nombreuses autres stratégies de « flow » (en série, waterfall, …) disponible avec async. Une de plus avancée je trouve (mais je n’ai pas encore trouvé une utilité) est auto
  • on a aussi accès à des fonctions permettant d’appliquer la même fonction à des collections (comme async.forEach(), async.map() …)

Le README du projet sur github est complet et décrit très bien toutes les fonctions offertes par la librarie.

Voici un 2ème exemple de callback qui illustre une génération d’erreur:

// test #2 - error
var async = require('async');

async.parallel({
    func3: function(callback){
    	// any function with callback
        setTimeout(function(){
            console.log('in function 3 callback');
            callback('error returned by function 3');
        }, 200);
    },
    func3: function(callback){
        setTimeout(function(){
            console.log('in function 4 callback');
            callback(null, 'function 4 result');
        }, 100);
    },
},
// optional callback
function(err, results){
	// we are here only if func3 and 4 called callback
	// or if one callback first param (err) is not null
	// not that results may contain some values (if some functions have ended)
        // only only one error, 0 to functionCount results
        // note: because we used the parallel map notation, result is a map
    console.log('test 2 - in final callback - err:', err, ' - results: ', results);
});

console.log('the end!');

Le résultat:

the end!
in function 4 callback
in function 3 callback
test 2 - in final callback - err: error returned by function 3  - results:  { func2: 'function 4 result', func1: undefined }

Voilà, j’apprends, doucement… Et, au cas où vous ne l’auriez pas compris… j’aime ça!!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *