Rock The Web with Node.js

Souvenir, souvenirs

1995 : création du langage LiveScript. Inspiré du Java, interprété, pour ecrire des serveur Http. A la fin de l'année la version embarquée dans Netscape navigator est renommée JavaScript (partenariat Sun)

1996 : Internet Explorer 3 embarque une VM pour le JavaScript, et le popularise sur le Web

2000 : Plusieurs implémentations (et VMs): JScript (Microsoft), ActionScript (Adobe), JavaScript (Gecko et SpiderMonkey)

2005 : Jesse James Garret écrit un papier sur l'Ajax, et décrit ce qui deviendra les RIA. C'est le point de départ de Dojo, Prototype, JQuery, MooTools

2008 : V8 (Dannois) est embarqué dans Chrome (et Chromium). JS 1.7 : let, yield

2009 : Ryan Dahl crée NodeJS basé sur V8, bientôt embauché par Joyent, puis soutient de Microsoft pour la compatibilité Windows

2010 : JavaScript 1.8.5 (ES5)

2011 : Vert.X est un framework évènementiel sur la JVM inspiré de NodeJS, utilisable en JS, Java, Coffee, Python, Groovy, Clojure, Scala

2011 : LinkedIn (16 Juillet 2011) pour son appli mobile, Walmart (Janvier 2012) pour son appli mobile & son site, eBay (17 mai 2011), Paypal (30 mai 2013), Groupon (7 Octobre 2013) ont intégré dans leurs SI du NodeJS

2015 : EcmaScript 2015 (ES6) approuvé le 17 juin

Sérieusement ? Du JS ?

Java (Groovy, Clojure, Scala), et .Net (C#, VB, Asp, F#) sont exécutés sur des machines virtuelles.

Les langages mordernes (Ruby, Go, Rust, Scala...) proposent une souplesse plus ou moins grande dans la gestion des types.

Il n'y a pas que le paradigme objet ! Les langages impératifs et fonctionnels ont des forces bien particulières.

Etude sur 30000 offres d'emploi au USA, Royaume Uni et Australie (juillet 2014).

Etude des salaires en ile de France

Evolution du salaire moyen pour NodeJS aux USA

Fullstack: une équipe capable de développer avec la même technologie les interfaces et les services métier

Ce qu'est Node.js

Pas un langage : on fait bien du JavaScript (pas de nouveaux mot clés)

Pas un framework : pas de cadre de programmation imposé, pas de finalité spécifique

Un environnement d'exécution : à l'instar des navigateurs, NodeJS exécute du JavaScript, mais dans un Système d'exploitation

JavaScript-Fu

En guise d'échauffement : (re)découverte du langage JavaScript.

Vos outils

Une fois Node.js installé, pensez a configurer les variables d'environnement HTTP_PROXY et HTTPS_PROXY

La console de votre navigateur, ou le REPL node sont aussi utilisables, mais beaucoup moins pratique pour l'édition.

Types & variables

Les string peuvent être délimitées avec des quotes simples ou doubles. Choisissez un délimiteur et tenez vous-y.

Utilisation des constructeurs possible mais déconseillée :
var isSimple = new Boolean(true);
à cause des égalité :
true !== new Boolean(true); et d'optimisations rendues difficiles

Les Object peuvent avoir autant de propriétés que vous voulez, à l'instar des dictionnaires (Python), hashs (Perl, Ruby), hash tables (C/C++), hash maps (Java), tableaux associatifs (Php)...

Ils sont très largement utilisés comme structures de données.

Types & variables

  1. Dans un JSBin, créez une variable Jack contenant la chaîne 'Hello I'm Jack'. Affichez le contenu de la variable dans la console.
  2. Affectez-y la valeur 18 et affichez Jack dans la console.
  3. Créez une variable Jill avec le constructeur Number et affectez lui la valeur 18.
  4. Vérifiez si Jack et Jill sont égaux (===) ou équivalents (==)

Les opérateurs de comparaison comparent bien les valeurs. == réalise un cast implicite des valeurs pointés, === n'en fait pas. C'est pourquoi il est recommandé de l'utiliser.

L'utilisation des constructeur (Number, Boolean, String) est déconseillée par rapport à l'utilisation des litéraux. La comparaison de deux instances ayant la même valeur sera toujours fausse : new Number(15) != new Number(15)

Correction

Pause

Types & variables

Ils proposent tous différents méthodes et attributs

Les tableaux sont dynamiques : leur longeur évolue (et il peuvent avoir des 'trous'). La longueur est stockée dans length

Date et Error n'ont pas de forme litérale.

Il existe bien d'autres "classes" : Int8Array, WeakMap, RangeError, Proxy...

Et oui, les fonction sont des objets (Functions are first-class citizen) ! Elles ont des attributs (arguments, length...) et des méthodes (apply, bind...)

4 syntaxes possibles pour les fonctions :
var sayHi = function() {...}
(expression),
function sayHi() {...}
(declaration),
var sayHi = function sayHi() {...}
(expression vers une closure nommée),
var sayHi = new Function('')
(création dynamique). On préfèrera la première solution.

Types & variables

  1. Créez un tableau days contenant les jours de la semaine, et faites un extrait des jours ouvrés dans une variable workingDays
  2. Affichez days, workingDays et le 3ème jour ouvré dans la console
  3. Créez une bière avec comme nom hoegaarden et 10 unités
  4. Créez une function drink qui accepte une bière et un nombre en paramètres. Elle décrémente le nombre d'unité de bière et renvoie une chaine avec le nom et le nombre de bières bues
  5. Créez une function doMove qui accepte une function, une bière et un nombre en paramètres. Elle renvoie une chaine avec la date courante et le résultat de la fonction sur les paramètres restants. Affichez le résultat de l'action de boire 2 bières

Les tableaux sont des objets, ils ont des méthodes comme slice().

Les object literals sont pratiques pour encapsuler des données dans une structure cohérente.

L'opérateur + permet de concaténer une chaînes de caractères avec une variable quelconque.

Les fonctions étant des objets, elles peuvent être passées en paramètres d'autres fonctions. Cela permet d'implémenter facilement le design pattern de délégation, ou pour "rappeler" le code appelant après une opération asynchrone.

Correction

Structures itératives

while et do while existent mais sont peu utilisées.

Si le tableau n'est pas modifié, il est courant de stocker sa longeur dans une variable pour accélerer le traitement (pratique discutable, surtout utile pour de vieux moteurs incapables d'optimiser).

Le for in parcours toutes les propriétés (attributs et méthodes) du prototype, y compris celles héritées. Pour les différencier, il est classique d'utiliser hasOwnProperty()

L'utilisation du for in est à proscrire sur le tableaux : les méthodes et les attributs du tableau seront également procéssés !

On peut même conseiller de ne pas utiliser for in pour les objets:
Object.keys(obj).forEach(function(value, attr) {
  /*...*/
});

L'utilisation du forEach est de plus en plus populaire, notamment grâce à underscore et lodash, surtout qu'il est de plus en plus performants (mais toujours moins que le simple for).

A l'intérieur d'une boucle for, les instructions break et continue permettent de contrôller l'éxécution de la boucle

Struct. conditionnelles

Comme beaucoup de langage inspirés du C, Javascript utiliser le short-circuit evaluation pour les conditions

Attention à ne pas oublier le break au niveau d'un switch

Le switch utilise une égalité stricte, attention lors de switches sur des types non int, bool, string

L'opérateur ternaire est utilisable dans une expression, ce qui le différentie du if else qui est une instruction.

Fonctions

En Javascript, les fonctions n'ont qu'un prototype, il y a une différence entre les paramètres déclarés et les paramètres effectifs à l'éxécution.

arguments est un pseudo-tableau contenant les paramètes effectifs au moment de l'appel.

Attention dans les fonctions critiques à la désoptimisation liée à arguments

Pas de portée bloc en JavaScript, une portée fonction.

Dans une fonction, toutes les variables sont initialisées avant la première instruction (hoisting)

Closures

Dans une fonction, les variable des scopes englobant sont toujours accessibles, sauf si une variable à l'intérieur de la fonction porte le même nom (shadowing)

L'exemple asynchrone aura pour résultat 3, 3, 3 et non pas 1, 2, 3 comme on aurait pû l'espérer

Pour corriger le problème de l'asynchronisme, il faut déclarer une fonction par itération, car la fonction crée un nouveau scope.
[0,1,2].forEach(function(i) {
  callLater(function() {
    console.log(i);
  });
});
ou encore
for(var i = 0; i < 3; i++) {
  (function(j) {
    callLater(function() {
      console.log(j);
    });
  })(i); // IIEF: Immediately-Invoked Function Expression
}

Structures & Fonctions

  1. Créez une fonction concat() qui concatène tous les arguments qui lui sont passé et les renvoie
  2. Améliorez cette fonction en utilisant la méthode reduce() des tableaux (Castez arguments au préalable)
  3. Créez une fonction getAttr() qui prend en paramètre un objet et un chemin (string) et renvoie l'attribut pointé par le chemin. Les étapes du chemin sont séparés par des '.' (entre dans les sous-objets). Si le chemin est invalide (étape inexistante), lancez une erreur avec throw.

Il faut utiliser arguments pour connaitre les véritables paramètres à l'appel de la fonction.

arguments n'est pas un vrai tableau : il n'a pas de fonction reduce, et il faut le "caster".

La fonction reduce prends 2 paramètres : une fonction de traitement, appliquée sur chaque élément du tableau, et dont le retour est stocké dans un accumulateur. Elle prend 2 paramètres : la valeur de l'accumulateur précédent, et l'élément courant. Le second paramètre de reduce est l'accumulateur initial.

getAttr() peut être réalisée récursivement ou itérativement.

Correction

Pause

Classes personnalisés

Dans la fonction Person, utilisée comme un constructeur, on stocke dans l'objet courant (this) une propriété.

Le prototype de la fonction Person est enrichi avec une propriété say.

L'opérateur new a pour effet de créer un nouvel objet qui sera référencé par this.

Il n'y a pas de propriété say dans l'objet dookie. Mais comme il y en à une dans son prototype, elle est utilisée.

Lorsqu'on utilise les opérateurs . ou [], le pointeur this référencie l'objet à gauche de l'opérateur.

A l'intérieur du constructeur, il aurait été possible d'affecter say dans this, plutôt que de le mettre dans le prototype. Cela accélère de manière sensible l'exécution, mais augmente la taille des instances (puisque la méthode est dupliquée dans chaque instance).

Tout ce qui est dans le prototype est donc partagé entre toutes les instances, mais ne peut servir à faire des variables à échelle de classe.

Attention : le mot clé this n'est jamais facultatif, comme en Java, C#.

Héritage de classes

L'ordre des instructions sur le prototype est crucial !

Il faut repositionner à la main la fonction constructeur car elle à été écrasée par l'affectation juste au dessus.

Object.create() est apparu en JS 1.8.5. Auparavant, il fallait utiliser un polyfill

Polymorphe par nature: la méthode de la sous classe sera toujours appelée en priorité sur la méthode de la classe mère.

Héritage minimaliste : pas d'interfaces, pas d'héritage multiple. Mais la création de traits est très simple par extension du prototype.

Pas d'encapsulation avec des notions de visibilité (private, protected...), mais faisable à base de closures.

Un article détaillé sur le sujet

Awful parts: scopes

Les linters vous aiderons à ne jamais oublier le mot-clé var

La ReferenceError signale une variable utilisé dans un scope où elle n'est pas définie.

Pour éviter le hosting, le mieux est d'être en ES6. Sinon, on peut se forcer à déclarer les variable en début de fonction (les linters peuvent aider).

Awful parts: scopes

Les règle de hoisting sont différente pour les variables et les définition de fonction. Pour obtenir un fonctionnement cohérent, on conseillera les assignation de fonctions

Le mot clé this pointe sur l'objet courant au moment de l'exécution, pas au moment de l'appel.

Lorsqu'une méthode est exécutée suite à un évènement asynchrone (timer, évènement navigateur, appel ajax, accès disque/réseau), l'objet courant n'est plus celui au moment de l'appel.

Il est courant d'utiliser une closure pour conserver le this (le célèbre

var self = this;
).

Awful parts: tableaux

length renvoi l'indice le plus grand (la taille occupée en mémoire donc), pas le nombre d'éléménts contenu.

Le for in ne garanti pas l'ordre de passage des clés, et parcours tous les élements enumérables du prototype.

La longueur du tableau ne tient pas compte des indices (des attributs) non numériques. Cette utilisation est destabilisante pour les lecteurs.

Awful parts: autres...

Les casts implicites sont pratiques, mais difficile à maîtriser, et il est préférable de s'en tenir aux égalités strictes pour une meilleur lisibilité.

TODO règles des ; implicites

Pourquoi il faut se passer d'eval

Modularité et I/O asynchrones

Modules NodeJS : export

RequireJS est un framework de dépendances entre fichier JS pour le navigateur (chargement dynamique), qui implémente la spécification Asynchronous Module Definition

CommonJS est une spécification pour utiliser le JS en dehors du navigateur, qui inclu une système de modularisation

EcmaScript 6 inclu un système de modularisation, dont on peut trouver diverses implémentations

NodeJS implémente uniquement la spec CommonJS.

En NodeJS il y a néanmoins un scope global, accessible depuis un module avec la variable global

Modules NodeJS : import

L'import relatif l'est par rapport au fichier (module) courant, et commence par ./ ou ../.

Le séparateur de chemin est toujours / quelque soit l'OS utilisé.

L'extension de fichier '.js', '.json' et '.node' sont facultatives: ../utils/common équivaut ../utils/common.js

Un import commençant par / est un import absolu. C'est une syntaxe déconseillée.

Modules NodeJS : a faire

Pas d'absolu pour éviter les problèmes de portabilité

Pas de relatif pour les dépendances externes, pour bien les repérer

Regrouper des dépendances externes pour NPM et pour la lisibilité

Ne pas dépendre de l'organisation interne d'une dépendance externe

les exports d'un module sont mis en cache (mémoire) par NodeJS, pour accélerer les futurs imports.

Il est possible d'avoir des dépendances cycliques, mais c'est compliquer a gérer.

Modules NodeJS

  1. Dans un dossier tps/modules, créez 4 modules (fichiers):
    1. Un pour les stocks, contenant l'objet beers
    2. Un pour les actions, contenant la fonction drink
    3. Un pour le pattern command, contenant la fonction doMove
    4. Un dernier, index.js, qui affiche dans la console le résultat de la commande de 4 drink sur les beers
  2. Pour exécutez votre programme depuis la ligne de commande
    tps/modules> node .
    ou
    tps/modules> node index.js
    .
  3. Rajoutez une action buy qui augmente le stocke d'un objet, et ajoutez à index.js l'exécution d'une commande d'achat de 2 bières.
  4. Transformez le fichier d'actions en dépendance externe (dans tps/modules/node_modules).

Ici, on différentira stocks.js et actions.js, qui publient plusieurs propriétés, de command.js, qui ne publie qu'une unique fonction.

On n'utilisera ici que des require de type relatif.

La création du module actions peut être réalisée de 3 manières :

  1. Un fichier actions.js dans le dossier node_modules
  2. Un fichier index.js dans le dossier node_modules/actions
  3. Un fichier x dans le dossier node_modules/actions accompagné du package.js qui le déclare comme fichier principal

Correction

Les opérations asynchrones

Contrairement à d'autre languages, Le JavaScript n'est pas interruptible

Il existe des version synchrone de certaines fonctionnalités sur les fichiers, mais jamais sur les sockets.

The Event Loop

Un article sur l'event loop en NodeJS, et une conférence sur l'event loop dans le navigateur

C'est généralement l'I/O qui est le plus couteux dans un programme, c'est quasiment systématique dans un service web.

Tant qu'il y a des instructions a exécuter, l'event loop ne dépile pas les callback.

Lorsque le processing devient trop important, il est nécessaire de le déporter dans un autre thread : une autre instance VM (à l'instar d'un webworker) voire un exécutable d'un autre langage.

Une exception levée dans un callback ne peut être catché par un try autour de la fonction asynchrone

La gestion des erreurs devient alors explicite et locale au callback

Pause

Le module fs

L'usage des fonctions en mode synchrone est a réserver à des cas très spécifiques, comme le chargement de configuration car il est alors normal que l'I/O soit bloquant au démarrage de l'application.

Les versions synchrones ne prennent pas de callback en paramètre, et leur retour est le résultat de l'opération, si une erreur a lieu il y a un throw d'Error.

Le module path

sep est le séparateur de fichier (/ sur Unix, \ sur Windows)

delimiter est le séparateur de chemins (: sur Unix, ; sur Windows)

File System

  1. Créez un module tps/fs/fs_utils.js qui exporte une fonction getDirContent().
  2. getDirContent() accepte 2 paramètres : le chemin du dossier, et une fonction callback.
  3. Lorsque vous aurez lu le dossier, invoquez le callback avec 2 paramètres : une erreur et le tableau contenant les noms des fichiers/sous-dossiers. Si le chemin est un dossier valide, le paramètre erreur doit être null.
  4. Testez votre fonction sur un fichier, un dossier invalide et un valide.
  5. Ajouter une fonction getDirStat() qui fonctionne comme getDirContent(), mais renvoi pour chaque fichier/sous-dossier : le chemin, le statut (file, directory) et la taille.

Pour lire le contenu d'un dossier, utilisez l'API Node

fs.readdir()
.

readdir() ne renvoi pas le chemin absolu des éléments : utilisez

path.resolve()
et
path.join()
pour les obtenirs.

La grande difficulté de getDirStat est dans l'utilisation d'une fonction asynchrone (

fs.stat()
)à l'intérieur d'une boucle. Diverses solutions existes, que nous verrons par la suite.

Correction

L'environnement d'exécution

La variable globale process

process est une variable globale de type object accessible n'importe où depuis votre programme

stdin est un Readable stream, stddout et stderr des Writable streams

console.log(), console.dir()... ne font qu'écrire dans process.stdout

Certaines propriétés du process courant peuvent être modifiées à chaud : setgid(), setuid(), kill()...

process est une instance d'EventEmitter

Il ne faut rien réaliser d'asynchrone dans l'auditeur d'exit, car le programme se terminera à la fin de ce dernier

uncaughtException est le try-catch de plus haut niveau : il ne doit pas être utilisé pour récupérer des exceptions "normales"

Il est généralement reconnu qu'il faille stopper le programme lorsqu'on reçoit une uncaughtException, et ce mécanisme existe à titre informatif

Le module os

loadavg() est un concept Unix, indiqué sous la forme d'un nombre réel, qu'il est généralement bien de maintenir en dessous du nombre de processeurs logiques

loadavg() est toujours nul sous Windows

Une partie des API de NodeJS sont écrites en C/C++, l'autre repose sur celle-ci

Et quelques bonus

console.X() sont des fonctions synchrones (à moins de configurer la sortie standard dans un pipe) : attention à leur impact sur l'exécution !

Comme nous le verrons plus tard, SetTimeout empile un callback dans la pile asynchrone

setTimeout(fn, 0) est particulièrement déconseillé, car il crée des timers système, là où process.nextTick() a une connaissance de l'event loop et s'y intègre à moindre coût

setInterval() est à manipuler avec précaution car il peut provoquer un phénomène d'engorgement

setImmediate() est déterministe et passe après l'I/O, les autres ne le sont pas

util.format() est une implémentation allégée de sprintf() qui supporte le JSON

util.inspect() est utilisé par console.dir()

util.inherits() permet de chainer les prototypes, et est utilisable en remplacement de Object.create() lors de la déclaration d'une classe fille

global est la variable globale qui permet d'écrire et lire dans un scope partagé entre tous les modules en cours d'éxécution

__filename est le nom de fichier ayant déclaré le code exécuté (chemin absolu)

__dirname est le nom du dossier dans lequel le script exécuté existe (chemin absolu)

// exécution de 'node example.js' dans /Users/a127380
console.log(__dirname); // /Users/a127380
console.log(__filename); // /Users/a127380/example.js

Environnement d'exécution

  1. Créez un module tps/process/uncaught.js qui déclenche la même fonction toutes les secondes : cette fonction lance une exception.
  2. Récupérez cette exception avec
    process.on('uncaughtException', function(err) { /*...*/})
    et au bout de 3 exceptions reçues, stoppez l'exécution avec un code 1.

C'est l'argument de process.exit() qui spécifie le code de fin d'exécution du programme : très utile pour les tests lors de l'intégration continue !

Pour afficher le code de sortie de la dernière commande, tapez echo $? sous Unix, echo %errorlevel% sous Windows CMD et echo $LastExitCode sous Windows Powershell

Dans la fonction qui s'enregistre toute les secondes et déclenche une exception, n'oubliez pas qu'aucune instruction ne peut être exécutée après un throw !

Pour prouvez que vous catchez correctement les 3 exceptions, il peut être utile de différentier leur message d'erreur et de les afficher

L'explication de l'inscription d'un auditeur d'évènement sera détaillée dans la prochaine partie de la formation

Correction

Récap du premier jour

Crédits photos