L’asynchronisme en JavaScript

Quiconque a déjà programmé en JavaScript, s’est déjà confronté aux notions de programmation asynchrone. Voyons 3 façons d’appréhender la problématique.

Que signifie « Asynchrone »

Plutôt que d’avoir une valeur retournée directement par votre fonction, vous passez une callback qui sera appelée lorsque le résultat sera disponible. Par exemple, lors d’un appel AJAX, durant le traitement du serveur, le thread JavaScript n’est pas bloqué en attendant le retour. Il traite d’autres instructions. Si ce n’était pas le cas, la page web serait paralysée pendant toute la durée du traitement serveur. L’idée est donc 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. Un message est mis dans la queue pour être traité une fois la réponse reçue. Ainsi, le navigateur peut continuer ses traitements pendant que l’appel HTTP est toujours en cours.

De par sa nature monithread, en JavaScript les I/O sont non bloquantes. Cela signifie qu’il n’y a pas d’attente de la réponse. Le code continue de s’exécuter. Le traitement de retour est positionné dans une callback qui sera exécutée quand la réponse surviendra.

Déroulement d’un appel AJAX:
1 – lancement de la requête AJAX
2 – Enregistrement d’une callback de retour dans la queue
3 – Traitement d’autres messages de la queue
4 – Exécution de la callback quand la réponse est arrivée. La stack doit bien évidement être vide pour traiter la réponse.

Exemple :

makeAjaxRequest("http://www.google.fr", function(response) {
    console.log("Retour appel ajax");
});
console.log("Fin");

Sortie de la console :

Fin
Retour appel ajax

On voit bien que la première instruction console.log n’est pas la première exécutée.

 

Patterns asynchrones

Callback

En JavaScript, les fonctions sont d’ordre supérieur c’est à dire qu’elles peuvent prendre une ou plusieurs fonctions comme entrée et renvoyer une fonction comme retour. De la première caractéristique découle qu’il est possible de passer une fonction en argument d’une autre et de l’exécuter plus tard dans le code. C’est le principe même des callbacks.
Une fonction de callback est une fonction passée en paramètre d’une autre fonction et appelée à l’intérieur de cette dernière.

Le callback hell est la complexité qui apparait lorsque l’on essaie de séquencer du code asynchrone utilisant des callbacks. Il en résulte du code imbriqué, difficile à lire et à maintenir. Le code ne se lit pas verticalement, la dernière ligne n’est pas la dernière instruction mais la plus profonde.

Exemple de callback hell aka pyramid of doom.

function1(args, function () {
    function2(args, function () {
        function3(args, function () {
            function4(args, function () {
                console.log('Done!');
            });
        });
    });
});

Le code est péniblement refactorable. Imaginez que vous souhaitiez inverser les fonctions 2 et 3 – et pour un peu plus de réalisme elles n’ont pas un nom similaire ni les mêmes arguments. Il vous faut coupez/coller la fonction 3 au dessus de la 2, réimbriquer le code et le réindenter. C’est assez laborieux.
Il est possible de nommer et d’extraire les fonctions, mais cela ne résout pas le problème

En nommant les fonctions de callback nous arrivons à quelques chose de mieux. La profondeur du code est moindre, mais il est tout de même difficile d’avoir une vue globale de l’enchainement des fonctions. Il faut naviguer de fonction en fonction pour découvrir le séquencement des appels.

function1(args, callback1);

function callback1() {
    function2(args, callback2);
}

function callback2() {
    function3(args, callback3);
}

function callback3() {
    function4(args, callback4);
});

function callback4 () {
    console.log('Done!');
});

 

Promesse

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. 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). Il est possible de chainer les promesses grâce à la méthode then. Pour cela, la fonction de traitement peut renvoyer une promesse ou une valeur brute qui sera passé en paramètre de la prochaine fonction.
Je ne rentre volontairement pas plus dans le détail car il existe de nombreux articles expliquant le fonctionnement des promesses.

Récrivons l’exemple précédent avec des promesses.

function1(args)
  .then(callback1)
  .then(callback2, callbackError2)
  .then(callback3)
  .then(callback4);

function callback1() {
    return function2(args);
}

function callback2() {
	return function3(args);
}
function callbackError2() {
	console.log("Error2");
}

function callback3() {
	return function4(args);
});

function callback4 () {
	console.log('Done!');
});

Le séquencement est immédiatement perceptible en regardant les « then ». La gestion des erreurs est également simplifiée. Si une promesse est rejetée, l’erreur se propage dans tout les then gérant une erreur.

Generator

Pour une présentation détaillé des générator, je vous invite à lire la présentation très complète de .

En synthèse, les générators arrivent dans ES6 et ne sont donc pas encore supportés par tous les navigateurs. Un générator est une fonction préfixée par le symbole « * ». La fonction est alors en attente jusqu’à ce qu’un appel à la méthode « next » soit effectué. Chaque appel à « next » exécute la fonction jusqu’au prochain « yield »

Exemple :

function simpleGenerator(){
  yield "first";
  yield "second";
  yield "third";
  for (var i = 0; i < 3; i++) {
    yield i;
  }
}

var g = simpleGenerator();
console.log(g.next()); // { value: "first", done: false }
console.log(g.next()); // { value: "second", done: false }
console.log(g.next()); // { value: "third", done: false }
console.log(g.next()); // { value: 0, done: false }
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: undefined, done: true }

Il est possible d’utiliser les générators pour créer des iterators ou faire de l’asynchronisme.
L’exemple qui suit est une illustration de ce qu’il est possible de faire. L’article de expose un exemple plus complet et plus robuste.

Exemple simple:

function request(url) {
    // this is where we're hiding the asynchronicity,
    // away from the main code of our generator
    // `it.next(..)` is the generator's iterator-resume
    // call
    makeAjaxCall( url, function(response){ //callback de succès
        it.next( response );
    },
	function(err) { // callback d'erreur
		it.throw(err);
	}
	);
    // Note: nothing returned here!
}

function *main() {
    var result1 = yield request( "http://some.url.1" );
    console.log("Retour appel 1 =>"+result1);

	// Gestion des erreurs pour cet appel AJAX
	try {
		var result2 = yield request( "http://some.url.2?id=" + result1.id );
		console.log( "The value you asked for: " + result2.value );
	}
	catch(e) {
		console.log("Erreur " +e);
	}
}

var it = main();
it.next(); // get it all started

La fonction main est un generator.

  1. it.next() exécute le generator jusqu’au premier yield
    La méthode request est exécutée, l’appel AJAX est donc lancé.
  2. Le generator est en pause.
  3. Le retour de l’appel AJAX déclenche la callback.
    La callback lance la commande it.next() en passant en paramètre les données de retour
  4. Le generator continue jusqu’au prochain yield
    La méthode request est exécutée, l’appel AJAX est donc lancé.
  5. Le generator est en pause.
  6. Le retour de l’appel AJAX déclenche la callback.
    La callback lance la commande it.next() en passant en paramètre les données de retour
  7. Fin

Hormis la présente de yield dans le code, la structure est identique à du code synchrone. Y compris la gestion des exceptions.

Sources

https://msdn.microsoft.com/fr-fr/library/windows/apps/hh700330.aspx
http://www.infoq.com/fr/news/2014/05/promises-javascript
http://www.infoq.com/fr/news/2014/05/promises-javascript
http://javascriptissexy.com/understand-javascript-callback-functions-and-use-them/
http://seajones.co.uk/do-we-need-to-beat-callback-hell/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators

Laisser un commentaire


8 + = quinze


NOTE - Vous pouvez utiliser les éléments et attributs HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>