BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Actualités Promesses : Nouveau standard JavaScript Asynchrone pour navigateurs ?

Promesses : Nouveau standard JavaScript Asynchrone pour navigateurs ?

Quiconque a déjà programmé en JavaScript, au delà des choses basiques, s'est déjà confronté aux notions de programmation asynchrone : plutôt que d'avoir une valeur retournée directement par votre fonction, vous passez un callback qui sera appelé lorsque le résultat sera disponible. Les opinions divergent sur la meilleure stratégie pour construire des applications d'envergure utilisant des API asynchrones. Cependant, l'ajout récent des promesses natives d'EcmaScript 6 (déjà disponibles dans les dernières versions de Chrome, Firefox et Opera) et leur adoption par les futures API des navigateurs pourraient changer la donne. Cela suffira-t-il à faire cesser le débat ? Maintenant que les promesses sont intégrées nativement, vont-elles devenir le nouveau standard de la programmation asynchrone dans le navigateur ?

Le problème

Qu'est-ce que la programmation asynchrone ? Supposons que l'on veuille récupérer un fichier mydata.json sur un serveur, depuis une page web. Si vous n'êtes pas familier avec le développement web, vous pourriez vous attendre à quelque chose comme ça :

var result = http.get("/mydata.json");
console.log("Got result", result);

Mais, sur le web, c'est soit impossible, soit considéré comme une mauvaise pratique. Cela s'explique car à la base, les navigateurs sont des environnements mono-threadé : vous avez une seule file d'exécution pour gérer le rendu de la page, les évènements et exécuter la logique. Du coup, si vous effectuez des traitements couteux ou lents sur ce thread, vous paralysez le navigateur pour toute la durée du traitement. C'est-à-dire que si la récupération de votre JSON prend 2 secondes, il ne ferait rien d'autre pendant 2 secondes. Ce n'est clairement pas une bonne approche et c'est pour cela que la plupart des appels couteux en JavaScript sont réalisés via des API_asynchrones_.

En général, l'idée est de ne pas bloquer le thread principal dans l'attente de la réponse et de fournir une fonction qui sera appelée lorsque le résultat sera disponible. Ainsi, le navigateur peut continuer ses traitements pendant que l'appel HTTP est toujours en cours. Prenons l'exemple classique de XmlHTTPRequest :

var req = new XMLHttpRequest();
req.open("GET", "/mydata.json", true);
req.onload = function(e) {
    var result = req.responseText;
    console.log("Got result", result);
};
req.send();

Ici, on crée un objet request, on y attache de quoi écouter l'évènement load. Lorsqu'il est déclenché, on continue le traitement.

Cet objet request avec émetteur d'évènements intégré est un modèle classique d'API de navigateur, mais on en trouve d'autres. Par exemple, l'API Geolocation qui permet de récupérer la position courante :

navigator.geolocation.getCurrentPosition(function(result) {
    console.log("Location", result);
}, function(err) {
    console.error("Got error", err);
});

Plutot que de retourner un objet requête, l'API getCurrentPosition prend plusieurs callbacks en paramètre, où le premier sera appelé en cas de succès (une position a été trouvée), et le second en cas d'erreur ou de problème.

Côté serveur, node.js embarque par défaut ce standard de callback unique à deux arguments (erreur et résultat) comme dernier paramètre des API asynchrones. Un fichier se lit donc ainsi en node.js :

var fs = require("fs");

fs.readFile("mydata.json", function(err, content) {
    if (err) {
        console.error("Got an error", err);
    } else {
        console.log("Got result", content);
    }
});

Chacun de ces systèmes a ses problèmes, entre autres :

  • La propagation des erreurs est manuelle. En programmation synchrone, on peut utiliser throw, try et catch pour gérer les erreurs. Ces mécanismes au niveau des langages ne fonctionnent pas dans un mode asynchrone. Pire encore, oublier de gérer correctement les erreurs peut facilement les faire disparaître ou simplement crasher le processus.
  • Les fonctions callback peuvent ne jamais être appelées ou appelées plusieurs fois. Si vous écrivez du code asynchrone, c'est assez courant d'oublier d'appeler son callback ou de l'appeler plusieurs fois. Dans les 2 cas, c'est difficile à debugger.
  • L'imbrication des callbacks est un problème courant. Ce problème est évitable mais il est très courant dans du code asynchrone.

Les promesses

Le but des promesses est de simplifier l'écriture de code asynchrone. Les API utilisant les promesses ne prennent pas de callback en paramètre mais renvoient un objet promesse. Un objet promesse n'a que quelques méthodes, dont la plus importante est then (c'est pourquoi on parle aussi parfois de "thenables"). La méthode then prend un ou deux arguments, le premier est appelé quand la promesse est résolue (succès) et le second lorsqu'elle est rejetée(erreur). Chacun de ces callbacks peut faire une des choses suivantes :

  • Retourner une nouvelle promesse. Dans ce cas, la résolution (succès ou erreur) est déléguée à cette nouvelle promesse. Dans les faits, on se sert de cette technique pour chaîner facilement des appels asynchrones et éviter l'imbrication.
  • Retourner une valeur. La promesse est considérée comme résolue avec cette valeur.
  • Envoyer une erreur. Si un throw est effectué dans la fonction, cela fera échouer la promesse.

Prenons un exemple. Supposons que l'on veuille récupérer la position de l'utilisateur et faire un appel AJAX au serveur avec cette position. Le code suivant est de l'asynchrone classique :

navigator.geolocation.getCurrentPosition(function(location) {
    var req = new XMLHttpRequest();
    req.open("PUT", "/location", true);
    req.onload = function() {
        console.log("Posted location!");
    };
    req.onerror = function(e) {
        console.error("Putting failed", e);
    };
    req.send(JSON.stringify(location.coords));
}, function(err) {
    console.error("Got error", err);
});

Comme vous pouvez le voir, nous avons deux dispositifs de traitement d'erreurs. Voyons maintenant le fonctionnement d'une potentielle version par promesse de ces API :

navigator.geolocation.getCurrentPosition().then(function(location) {
    var req = new XMLHttpRequest();
    req.open("PUT", "/location", true);
    return req.send(JSON.stringify(location.coords));
}).then(function() {
    console.log("Posted location!");
}).then(null, function(err) {
    console.error("Got error", err);
});

Quelques informations concernant cette version par promesse :

  • L'imbrication ne dépasse pas un niveau.
  • Les erreurs sont toutes gérées ensemble. Si une erreur est générée dans le premier appel, elle est propagée jusqu'à ce qu'un then la gère.

Les promesses ont globalement plus d'avantages, vous pouvez retrouver plusieurs sources et lectures à la fin de cet article.

Le futur

Depuis Chrome 32, Firefox 29 et Opera 19, les promesses font partie des API du navigateur via le constructeur Promise. De plus, les futures API devraient les utiliser. Par exemple :

Avec le développement des promesses dans les API des navigateurs, cela favorisera-t-il leur adoption par les bibliothèques de code ? jQuery possède déjà sa propre implémentation deferred. Avec les générateurs d'EcmaScript 6, les promesses gagnent encore plus avec la possibilité d'écrire du code qui semble synchrone mais qui s'exécute comme de l'asynchrone.

Pour en savoir plus sur les promesses et leurs avantages, l'article de Jake Archibald sur HTML5Rocks est un bon point de départ. Si vous ciblez des navigateurs plus anciens, il existe un petit polyfill. Domenic Denicola a également fait plusieurs présentations et a été un défenseur des promesses depuis longtemps. Une spécification pour les promesses est disponible : proposition Promises/A+. Elles sont hébergées sur le réseau développeur de Mozilla.

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT