samedi 29 octobre 2011

Les evenements #Node.js et #cofffeescript

J'ai evoqué dans un  précedent post, l'importance des fonctions de retour  (callback) dans l'architecture d'une application asynchrone. Gérer une suite d'action asynchrone par un chainage de callback se révèle vite très difficile. Aussi , il existe un deuxième composant venant en complément des callback : les évènements.
Node.js fournit une librairie pour gérer les évènements : require ('event'). Les évènements sont le véritable centre nerveux d'une application asynchrone dans Node.js. Ils  émettrent  des signaux qui  associés à des callback, orientent et cadencent  le déroulement du programme.
Exemple:
Les opérations pour  interroger une  page WEB les suivantes:
1) Se connecter au serveur
2) Lancer la requête
3) Traiter les entêtes
4) Traiter le corps de la page
4) Se déconnecter

Pour ne pas bloquer le système entre chaque étape notamment celle qui attend la réponse du serveur, il est judicieux de programmer l'émission de signaux à des moments clés du cycle. Exemple 'j'ai reçu les entetes'  , 'j'ai  reçu les données de la page' et 'le serveur m'indique que c'est terminé'.  pour chacun de ces signaux, il sera nécessaire de les 'ecouter' et de réagir  en  lançant les actions associées (callback) .
Ce système est composé de deux parties: l'emetteur de signaux (evenement) et le recepteur (listener) qui attend les évenements et lance les actions (callback ) prévues.


Les API de base de node.js sont livrées avec une gestion d’évènement, en charge pour le developpeur de réaliser la partie qui va traiter ces évènements.
(Exemple API HTTP: documentation ici )



  Avec  ici un exemple de récepteur sur l’évènement 'data':


  res.on('data', function (chunk) {
    console.log('BODY: ' + chunk);
  });
Comment mettre en place son propre système d’évènement.

La partie réception et traitement est la plus facile en voici un exemple:

var signal=require('./signal.js');
var sonde = new signal();
// mise en place des listener et des fonctions de retour
sonde.on('ici',function(msg){
console.log(msg)} );
sonde.on('lemoment',function(msg){
console.log(msg)} );
// lancement des opérations
sonde.temporisateur();
sonde.maint();
view raw liste.js hosted with ❤ by GitHub

Deux récepteurs sont mis à l'écoute.

La partie émission des signaux.

Node.js fournit la libraire 'event' et voici un exemple en coffeescript qui fonctionne avec le programme précédent:

{EventEmitter} = require('events')
class Signal extends EventEmitter
maint: ->
@emit 'ici','maintenant'
temporisateur: ->
setTimeout ( => @emit 'lemoment',"le message est arrive") , 5000
module.exports = Signal
view raw Signal.coffee hosted with ❤ by GitHub

Il ne faut pas grand chose ! C'est la magie de coffeescript et la puissance de node.js.
Pourquoi la magie de coffesscript ?
Parce en javascript écrit à la main, dans les règles de l'art,  cela donne:

var events = require('events');
/*
* Define a non-enumerable function to allow ease of extending objects.
*/
Object.defineProperty(Object.prototype, "extend", {
enumerable: false,
value: function (from) {
var props = Object.getOwnPropertyNames(from);
var dest = this;
props.forEach(function(name) {
var descriptor = Object.getOwnPropertyDescriptor(from, name);
Object.defineProperty(dest, name, descriptor);
});
return this;
}
});
/*
* Define the main api_request contsructor, also inheirit from EventEmmitter
*/
function signal (data) {
// Call EventEmitter constructor on this context
events.EventEmitter.call(this);
}
signal.super_ = events.EventEmmitter;
signal.prototype = Object.create(
events.EventEmitter.prototype,
{
constructor: {
value: signal,
enumerable: false
}
}
);
signal.prototype.temporisateur = function() {
var self = this;
setTimeout(function(){self.emit('lemoment',"mon message");},5000);
};
signal.prototype.maint = function() {
this.emit('ici',"maintenant");
};
module.exports = signal;
view raw signal.js hosted with ❤ by GitHub

La premiere fonction sert à construire proprement des objets, sans effet de bord sur d'autres modules.
(copiée sur api_request )

Il est intéressant de voir que tous ces problèmes de constructeur sont pris en charge par coffescript comme le montre le javascript issu du programme coffee. 
(function() {
var EventEmitter, Signal, querystring;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
}, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
EventEmitter = require('events').EventEmitter;
querystring = require('querystring');
Signal = (function() {
__extends(Signal, EventEmitter);
function Signal() {
Signal.__super__.constructor.apply(this, arguments);
}
Signal.prototype.maint = function() {
return this.emit('ici', 'maintenant');
};
Signal.prototype.temporisateur = function() {
return setTimeout((__bind(function() {
return this.emit('lemoment', "le message est arrive");
}, this)), 5000);
};
return Signal;
})();
module.exports = Signal;
}).call(this);

Aussi, coffescript n'est pas un simple générateur de code. Il met en place les bonnes pratiques et vous économise des lignes de code.



Aucun commentaire: