Utilisez la méthode Function.prototype.bind() de Javascript

Chaque développeur javascript a un jour ou l’autre rencontré un problème avec le contexte associé à this.

Prenons l’exemple de code simpliste suivant :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  }
};

myObject.doSomethingCrazy();

Mon objet contient une propriété crazyMessage et une méthode doSomethingCrazy qui utilise la propriété crazyMessage.
Jusqu’ici tout va bien, le contexte de this au sein de la méthode doSomethingCrazy est bien l’objet myObject.

Maintenant, dans un élan de folie, je veux implémenter la méthode doSomeAsyncCrazyness :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Aussi surprenant que cela puisse paraître, je récupère une erreur dans ma console :

Uncaught TypeError: undefined is not a function 

Cette erreur est due à this qui est désormais associé au contexte global et non plus à mon objet myObject, à cause de l’appel à setTimeout.

Mettre this en « cache »

Pour pallier ce problème, l’approche très souvent adoptée par le développeur est d’assigner this à une variable intermédiaire temporaire de type that ou self de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {

    var that = this;
    setTimeout(function() {
      that.doSomethingCrazy();
    }, 1000);
  }
};

myObject.doSomeAsyncCrazyness();

De cette manière, l’appel à la méthode doSomeAsyncCrazyness fonctionne en effet comme prévu.

Une méthode bien plus élégante existe cependant et ce depuis ECMAScript 5.

.bind() magic

La méthode .bind() disponible sur le prototype de Function résoud notre problème en permettant, entre autres choses, de définir explicitement le contexte associé à this.

Nous pouvons donc réécrire notre code précédent de la manière suivante :

var myObject = {

  crazyMessage: 'gouzigouza',

  doSomethingCrazy: function() {
    alert(this.crazyMessage);
  },

  doSomeAsyncCrazyness: function() {
    setTimeout(function() {
      this.doSomethingCrazy();
    }.bind(this), 1000);
  }
};

myObject.doSomeAsyncCrazyness();

Avouez que c’est quand même plus joli.

Support des vieux navigateurs

Et si mon code doit s’exécuter dans un navigateur qui ne supporte pas ES5 ? Internet Explorer < 9 pour ne pas le nommer.
Dans ce cas, plusieurs solutions.

  • Continuer avec la technique de mise en cache de this
  • Utiliser la méthode .bind implémentée dans la plupart des frameworks JS (Angular.bind() par exemple)
  • Implémenter un fallback quand la méthode n’est pas disponible

Mozilla notamment propose le fallback suivant (Mozilla polyfill Function.prototype.bind()) :

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - 
           what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

Pour aller plus loin

Quelques ressources pour aller plus loin :

Designez des API asynchrones VRAIMENT asynchrones

Je suis tombé récemment sur un vieil article d’Isaac Zimmitti Schlueter à lire ABSOLUMENT.

Pour résumer rapidement le propos, IZS explique suite à un avertissement on ne peut plus clair (Do Not Release Zalgo) que lors du design d’une API Asynchrone, il faut éviter à tout prix la situation suivante :

var cachedData;
function getSomeData(callback) {
  if (cachedData === undefined) {
    $.get('some/random/api').then(function(data) {
      callback(cachedData = data);
    });
  } else {
    callback(cachedData);
  }
}

Quel est le problème avec ce bout de code ?

Si vous êtes développeur AngularJs / EmberJs / votre framework javascript préféré, je suis certain que vous avez vous-même été amené à écrire du code similaire ou au moins à en rencontrer au sein de vos applications.

Alors quel est le problème ?

Mon API fait un calcul coûteux (requête HTTP vers une API tiers) au premier appel, stocke la donnée retournée dans une variable servant de cache et appelle le callback. Au deuxième appel, mon API constate que la donnée est déjà en cache et appelle immédiatement le callback.

L’utilisation d’une API de ce type est extrêmement complexe et source de bugs. Pourquoi ? Son comportement est difficile à prédire…

L’auteur l’explique en ces termes sur son article :

If you have an API which takes a callback, and sometimes that callback is called immediately, and other times that callback is called at some point in the future, then you will render any code using this API impossible to reason about, and cause the release of Zalgo.

Prenons l’exemple du code javascript suivant :

// Quelque part dans mon code
getSomeData(function(result) {
  // Faire quelque chose avec mon résultat
});

function clickHandler() {
  var data;
  getSomeData(function(result) {
    data = result;
  });

  if (data.id === 1) {
    // Traitement important
    alert('gouzigouza');
  }
}

Ligne 2, on appelle notre API asynchrone. Un peu plus bas, on définit une fonction appelée lors d’un clic sur un bouton dans une page web. Au sein de ce clickHandler on utilise l’objet data en partant du principe qu’il est disponible immédiatement. L’erreur saute ici évidemment aux yeux. Rien ne garantit que notre objet data ait déjà été retourné arrivé à la condition if.

Malheureusement, le design de notre API et l’utilisation qui en a été faite ici fait que le bug n’est pas identifiable immédiatement. En revanche, si le développeur supprime le premier appel à notre API ligne 2 ou si le clic sur le bouton au sein de la page web intervient avant que le résultat de notre API asynchrone n’ait été mis en cache, alors on se retrouvera avec une belle erreur javascript :

TypeError: Cannot read property 'id' of undefined

Quelle solution ?

Pour une API asynchrone, l’objectif est donc de toujours respecter le caractère asynchrone.
Pour se faire, une approche simpliste est de modifier notre API de la manière suivante :

var cachedData;
function getSomeData(callback) {
  if (cachedData === undefined) {
    $.get('some/random/api').then(function(data) {
      callback(cachedData = data);
    });
  } else {
    setTimeout(function() {
      callback(cachedData = data);
    }, 0);
  }
}

Ici, l’utilisation du setTimeout() est une astuce qui permet de forcer l’appel du callback de manière asynchrone. Si l’API était codée avec nodejs, il serait bien plus judicieux d’utiliser process.nextTick() qui permet de mettre en place un comportement similaire.

Ainsi, à l’usage, il est désormais systématiquement impossible de faire fonctionner le code suivant :

var data;
getSomeData(function(result) {
  data = result;
});

if (data.id === 1) {
  // Bug systématique
  alert('gouzigouza');
}

Le développeur identifie immédiatement son erreur et est obligé alors de modifier son code de la manière suivante :

getSomeData(function(result) {
  var data = result;
  if (data.id === 1) {
    alert('gouzigouza'); // OK
  }
});

Pour aller plus loin

Voici quelques liens pour approfondir le sujet. Je vous conseille évidemment la lecture de l’article de IZS qui ajoute des exemples de design pour des API où les données sont généralement présentes immédiatement et où les performances sont très importantes.
Vous pouvez également aller jeter un oeil à la partie Keeping callbacks truly asynchronous de cet article sur la fonction process.nextTick de nodejs.

EmberJs – Construire une application autour de panels et d’onglets

Le router de emberjs se base massivement sur des conventions de nommage qui rendent ultra rapide le développement d’une application, notamment si celle-ci est basée sur un workflow standard de type CRUD.
Je liste mes entités, je peux voir une entité, je peux éditer une entité…
Exemples d’URLs de ce type :

  • /posts
  • /posts/1/show
  • /posts/1/edit
  • /authors
  • /authors/1/show
  • /authors/1/edit

Dans ce genre de cas, il n’est presque jamais nécessaire de surcharger les routes générées en mémoire par ember.

En revanche, quand une application devient un peu plus complexe dans sa structure, il devient indispensable de définir des routes spécifiques.

Aujourd’hui, je vais présenter une approche permettant de construire une application emberjs autour d’un layout découpé en panneaux (panels), chacun de ces panneaux étant capable d’afficher un ou plusieurs onglets. Evidemment, en ayant comme objectif que l’état de ces différents onglets soit géré par le router pour pouvoir le conserver via l’URL sur un reloading de la page.

Ci-dessous un petit schéma pour expliciter visuellement le contexte :

Application ember panes with tabs

Commençons par définir notre application et le layout associé :

// main.js
App = Ember.Application.create({});
// index.html
<script type="text/x-handlebars" data-template-name="application">
  <h1>This is a sample app</h1>
  {{outlet leftPanel}}
  {{outlet rightPanel}}
</script>

Pour savoir en permanence quels sont les onglets sélectionnés dans chacun des panneaux de gauche et de droite, il nous faut les stocker quelque part. L’endroit idéal est le contrôleur d’application qui se trouve hiérarchiquement au dessus de nos deux panels.
On lui ajoute donc deux propriétés leftTab et rightTab auxquelles on associe les onglets à afficher par défaut.
Ensuite, il nous faut définir les contrôleurs pour le panneau de gauche et le panneau de droite. Ils restent vides dans un premier temps mais ce sont eux qui s’occuperont de notifier le router lorsque l’utilisateur souhaite changer l’un ou l’autre des onglets sélectionnés.

App.ApplicationController = Ember.Controller.extend({
    leftTab: 'leftPanelTab1',
    rightTab: 'rightPanelTab1'
});
 
App.LeftPanelController = Ember.Controller.extend({
    // ...
});
 
App.RightPanelController = Ember.Controller.extend({
    // ...
});

Ajoutons les templates associés à nos panneaux. Ces derniers contiennent les liens vers les différents onglets et un outlet dans lequel notre router va injecter l’onglet sélectionné :

<script type="text/x-handlebars" data-template-name="leftPanel">
<div>Left panel
    <a href="#" {{action selectTab "leftPanelTab1"}}>Left panel tab 1</a>
    <a href="#" {{action selectTab "leftPanelTab2"}}>Left panel tab 2</a>
    {{outlet}}
</div>
</script>

<script type="text/x-handlebars" data-template-name="rightPane">
<div>Right panel
    <a href="#" {{action selectTab "rightPanelTab1"}}>Right panel tab 1</a>
    <a href="#" {{action selectTab "rightPanelTab2"}}>Right panel tab 2</a>
    {{outlet}}
</div>
</script>

Pour injecter nos panneaux au sein de notre layout, il est nécessaire d’implémenter la route ApplicationRoute :

App.ApplicationRoute = Ember.Route.extend({
    renderTemplate: function() {
        this.render();
        this.render('leftPanel', {
            outlet: 'leftPanel',
            into: 'application'
        });
 
        this.render('rightPanel', {
            outlet: 'rightPanel',
            into: 'application'
        });
    }
});

Pour notifier notre application que l’utilisateur souhaite changer l’onglet de l’un ou l’autre des panneaux, on utilise ici un helper action pour déclencher une action au niveau de nos contrôleurs, en lui spécifiant en paramètre l’onglet cliqué.

Il nous faut donc maintenant intercepter ces actions dans les contrôleurs et remonter l’information au niveau de notre router en les modifiant ainsi :

App.LeftPanelController = Ember.Controller.extend({
    selectTab: function(tab) {
        this.send('leftTabChanged', tab);
    }
});
 
App.RightPanelController = Ember.Controller.extend({
    selectTab: function(tab) {
        this.send('rightTabChanged', tab);
    }
});

Pour représenter l’état de nos panneaux (quels onglets sont affichés), nous allons implémenter une route prenant en paramètres l’onglet à afficher dans chaque zone.

App.PanelsRoute = Ember.Route.extend({
    serialize: function(params, paramNames) {
        return params;
    },
    renderTemplate: function(controller, params) {
 
        this.render(params.leftTab, {
            into: 'leftPanel'
        });
 
 
        this.render(params.rightTab, {
            into: 'rightPanel'
        });
    }
});

Et pour obtenir une belle URL associée, nous pouvons définir le mapping suivant :

App.Router.map(function() {
    this.resource('panels', { path: '/:leftTab/:rightTab' });
});

Enfin, pour terminer et lier l’ensemble, nous allons modifier notre ApplicationRoute afin d’y gérer les évènements associés aux changements d’onglets.
Quand un évènement nous notifiant du changement d’onglet dans le panneau de gauche remonte jusqu’au router, on redirige vers la route panels en lui injectant les nouveaux onglets sélectionnés.

App.ApplicationRoute = Ember.Route.extend({
    events: {
        leftTabChanged: function(tab) {
            this.controllerFor('application').set('leftTab', tab);
            this.transitionTo('panels', {
                leftTab: tab,
                rightTab: this.controllerFor('application').get('rightTab')
            });
 
        },
        rightTabChanged: function(tab) {
            this.controllerFor('application').set('rightTab', tab);
            this.transitionTo('panels', {
                leftTab:this.controllerFor('application').get('leftTab'),
                rightTab:tab
            });
        }
    },

    renderTemplates: // ...
}

Une autre solution pour répondre à ce même problème serait d’injecter dans nos controlers de panels l’onglet sélectionné dans le panneau opposé. On peut dans ce cas simplifier le code et utiliser directement des helpers linkTo. La solution présentée ici offre néanmoins une grande souplesse pour opérer des traitements intermédiaires lors de la selection par l’utilisateur d’un onglet dans un ou l’autre des panneaux.

Ci-dessous le code complet de cet exemple :

App = Ember.Application.create({});

App.Router.map(function() {
    this.resource('panels', { path: '/:leftTab/:rightTab' });
});

App.ApplicationRoute = Ember.Route.extend({
    events: {
        leftTabChanged: function(tab) {
            this.controllerFor('application').set('leftTab', tab);
            this.transitionTo('panels', {
                leftTab: tab,
                rightTab: this.controllerFor('application').get('rightTab')
            });

        },
        rightTabChanged: function(tab) {
            this.controllerFor('application').set('rightTab', tab);
            this.transitionTo('panels', {
                leftTab:this.controllerFor('application').get('leftTab'),
                rightTab:tab
            });
        }
    },

    renderTemplate: function() {
        this.render();
        this.render('leftPanel', {
            outlet: 'leftPanel',
            into: 'application'
        });

        this.render('rightPanel', {
            outlet: 'rightPanel',
            into: 'application'
        });
    }
});

App.IndexRoute = Ember.Route.extend({
    redirect: function() {
        this.transitionTo('panels', {
            leftTab: this.controllerFor('application').get('leftTab'),
            rightTab: this.controllerFor('application').get('rightTab')
        });
    }
});

App.PanelsRoute = Ember.Route.extend({
    serialize: function(params, paramNames) {
        return params;
    },
    renderTemplate: function(controller, params) {

        this.render(params.leftTab, {
            into: 'leftPanel'
        });


        this.render(params.rightTab, {
            into: 'rightPanel'
        });
    }
});

App.ApplicationController = Ember.Controller.extend({
    leftTab: 'leftPanelTab1',
    rightTab: 'rightPanelTab1'
});

App.LeftPanelController = Ember.Controller.extend({
    selectTab: function(tab) {
        this.send('leftTabChanged', tab);
    }
});

App.LeftPanelView = Ember.View.extend({
    classNames: ['pane']
});

App.RightPanelController = Ember.Controller.extend({
    selectTab: function(tab) {
        this.send('rightTabChanged', tab);
    }
});

App.RightPanelView = Ember.View.extend({
    classNames: ['pane']
});
<script type="text/x-handlebars" data-template-name="application">
  <h1>This is a sample app</h1>
  {{outlet leftPanel}}
  {{outlet rightPanel}}
</script>

<script type="text/x-handlebars" data-template-name="leftPanel">
    <a href="#lefttab1" {{action selectTab "leftPanelTab1"}}>Left pane tab 1</a>
    <a href="#lefttab2" {{action selectTab "leftPanelTab2"}}>Left pane tab 2</a>
    <div class="tab-container">{{outlet}}</div>
</script>

<script type="text/x-handlebars" data-template-name="leftPanelTab1">
Left Panel Tab1
</script>

<script type="text/x-handlebars" data-template-name="leftPanelTab2">
Left Panel Tab2
</script>

<script type="text/x-handlebars" data-template-name="rightPanel">
    <a href="#righttab1" {{action selectTab "rightPanelTab1"}}>Right pane tab 1</a>
    <a href="#righttab2" {{action selectTab "rightPanelTab2"}}>Right pane tab 2</a>
    <div class="tab-container">{{outlet}}</div>
</script>

<script type="text/x-handlebars" data-template-name="rightPanelTab1">
    Right Panel Tab1
</script>

<script type="text/x-handlebars" data-template-name="rightPanelTab2">
Right Panel Tab2
</script>

Le résultat est visible sur ce jsfiddle : http://jsfiddle.net/obalais/L78yu
Et le gist associé ci-besoin : https://gist.github.com/bobey/5118200

Premiers pas avec Node.js – Installation de Node et Express

Edit du 26/11/2012

Depuis la rédaction de cet article, nodejs et npm ont beaucoup évolué (A l’époque, node était en version 0.4, il est désormais en 0.8 !).
La manière la plus simple de les installer à mon sens est de passer par le package manager de sa distribution.
Sous Ubuntu :

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs npm

RDV ici pour plus d’infos : https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager

Article d’origine

Chez PMSIpilot, à chaque fin de Sprint, les équipes techniques organisent et participent à ce qu’on appelle les « ateliers ». Comme tout bon geek qui se respecte, j’ai une soif insatiable d’apprendre de nouvelles choses et je participe donc en général au plus grand nombre possible de ces ateliers. Un de ceux qui m’ont le plus marqué est sans aucun doute celui qui présentait le fonctionnement et l’utilisation de Node.js.

Je vais donc profiter de ce blog pour faire un retour au fur et à mesure de ma découverte de Node.js et de son écosystème.
Ce premier billet résume l’installation de Node et Express et me servira de mémo pour retrouver facilement les commandes qui vont bien.

Node.js est un framework très en vogue qui consiste à utiliser le langage Javascript avec le moteur V8 de Google côté serveur pour servir les requêtes HTTP reçues.

Parmis les intérêts majeurs de ce type d’approche on retient notamment :

  • L’approche évènementielle du framework (entrées / sorties non bloquantes…)
  • La possibilité d’utiliser un seul langage côté serveur et côté client pour les applications Full JS

Ainsi, grâce à sa légèreté et à ses I/O non bloquants, Node.js permet de développer une application web capable de recevoir et de répondre à un nombre de requêtes infiniment plus conséquent qu’une même application développée avec une pile LAMP classique.
C’est toute la philosophie du développement web qui se voit bouleversée par cette approche évènementielle.

Pour plus d’informations à ce sujet, je vous invite à lire les articles suivants :

Let’s get started!

Première étape, télécharger et installer Node.js. Pour se faire RDV sur le site officiel pour télécharger la dernière release en date : http://nodejs.org/#download. Décompressez l’archive, rendez-vous dans le dossier et exécutez les commandes suivantes une par une :

mkdir ~/local
./configure --prefix=$HOME/local/node
make
make install
export PATH=$HOME/local/node/bin:$PATH

La commande make prend un certain temps à s’exécuter donc il faut s’armer de patience. Ensuite le reste est trivial.
A noter que si au moment du lancement de configure, vous avez une erreur du type Could not autodetect OpenSSL support. Make sure OpenSSL development packages are installed, c’est qu’il vous manque la lib OpenSSL.
Dans ce cas, un petit apt-get résoud le problème :

sudo apt-get install libssl-dev

Dans le même ordre d’idée, il se peut qu’un message vous avertisse qu’il manque un compilateur du type g++ / c++. Dans ce cas :

sudo apt-get install g++

Voilà une bonne chose de faite. Testez l’installation en exécutant la commande suivante :

obalais@server:~$ node -v
v0.4.7

Même s’il est possible de créer une application web complète juste avec Node.js, nous allons installer des plugins qui vont nous faciliter le développement. Un framework MVC (Express) par exemple me semble plus que nécessaire, un moteur de template (Jade) ainsi qu’un ORM pour la base de données (Mongoose si on utilise une base MongoDb).
Nous avons de la chance puisqu’un gestionnaire de paquets spécifique à Node.js est devenu plus ou moins un standard pour récupérer facilement les plugins qui existent, à savoir NPM.

Si vous n’avez pas curl :

sudo apt-get install curl

Ensuite, l’installation de NPM est on ne peut plus simple puisqu’il suffit d’une seule ligne :

curl http://npmjs.org/install.sh | sh

Et une fois de plus, vous pouvez tester si l’installation s’est bien déroulée en lançant la commande suivante :

obalais@server:~$ npm -v
1.0.6

Nous allons tout de suite mettre à profit NPM en installant Express, le framework de développement web le plus abouti actuellement pour Node.js.

Voici la liste de ses features, directement récupérée depuis le site officiel :

  • Robust routing
  • Redirection helpers
  • Dynamic view helpers
  • Application level view options
  • Content negotiation
  • Application mounting
  • Focus on high performance
  • View rendering and partials support
  • Environment based configuration
  • Session based flash notifications
  • Built on Connect
  • Executable for generating applications quickly
  • High test coverage

Grâce à NPM, l’installation se fait encore une seule ligne toute simple :

npm install express

N’oubliez pas de rajouter express à votre PATH et de tester le résultat de l’installation :

obalais@server:~$ export PATH=$HOME/node_modules/express/bin:$PATH
obalais@server:~$ express --version
2.3.4

Maintenant installez Jade :

npm install jade

Tout est en place. Il ne reste plus qu’à créer notre serveur avec Node.js + Express. Pour cela, créez un fichier helloworld.js contenant le code suivant :

var app = require('express').createServer();

app.get('/', function(req, res){
  res.send('<h1>another hello world</h1>');
});

app.listen(3000);

On lance le serveur node.js avec la commande suivante :

node helloworld.js

Et on teste si tout fonctionne correctement en se rendant via son navigateur sur son localhost sur le port 3000.

Nous verrons dans un prochain article quels sont les principes fondamentaux du développement avec Express et Node.js.
En attendant, je vous laisse visiter le site officiel d’Express qui contient toute la documentation nécessaire pour se lancer dans l’utilisation du Framework. Faites un tour sur cette page pour commencer.