callback hell), mais empêche également l'exécution concurrente des différents appels :
Suivant l’idée développée dans le premier point de l’article, il serait donc intéressant de paralléliser ces appels.
Cela est possible en utilisant les promises et le service $q d’Angular, rendant les appels au service totalement indépendants.
Chaque promise est stockée dans un tableau, qui est ensuite traité par la fonction $q.all(), point de synchronisation, attendant le résultat de toutes les promises et renvoyant le résultat final.
Les promises sont exécutées ainsi en parallèle, avec à la clé un gain de temps non négligeable.
Voici l’ensemble des scripts chargés à l’appel de toutes les pages de notre application :
L’inspection faite par l’outil Chrome Inspector, nous indique que près de 2 secondes sont nécessaires à l’appel de ces scripts à chaque chargement de notre application.
La timeline ci-dessus nous permet également de remarquer le comportement du navigateur, traitant les requêtes par paquet et en cascade car seulement capable de paralléliser un nombre limité d’appels.
Ces appels multiples ont donc un coût en terme de temps de chargement qu’il est nécessaire de réduire.
Cela est réalisable grâce à UglifyJs, outil permettant de concaténer et “minifier” les fichiers JavaScript afin d’accéder à l’ensemble des scripts en ne réalisant qu’une seule requête, rendant ainsi le chargement de la page plus rapide (disponible en plugin du builder JavaScript Grunt).
Nous passons ainsi de :<br><br> | à: |
et à une requête réalisée en 459 ms, soit une rapidité d'exécution bien supérieure à notre cas initial :
Toutefois, cette action “magique” n’est pas sans conséquence et nous comprenons mieux l’origine du nom de l’outil en parcourant le contenu du fichier généré :
Cette illisibilité rend ce fichier très difficile à débugger, aussi “l’uglification” des scripts est une action à ne réaliser qu’une fois, au déploiement de l’application en production.
A noter qu'on peut aussi se contenter de concaténer les fichiers sans les modifier, avec le plug-in grunt-contrib-concat de NodeJs.
Angular propose un module de cache très simple à mettre en oeuvre, en initialisant la propriété ‘cache’ du service $http à ‘true’ :
$http.get(URL, { cache: true })
Ainsi, la première fois que le service $http enverra une requête à une URL, la réponse sera stockée dans un cache nommé $http.
Ce cache est accessible grâce au service $cacheFactory:
Les requêtes basées sur la méthode GET bénéficient du principe d'idempotence REST qui permet d'exploiter naturellement un système de cache, ce n'est malheureusement pas le cas des méthodes POST dans les principales requêtes (SOAP) de l'application. Une API REST aurait donc été un grand avantage dans ce cas.
Cette option a ainsi amélioré certains aspects de l’application mais n’a pas réalisé l’optimisation principalement attendue : éviter les transactions client-API pour une même requête.
L’API utilisant le protocole SOAP, il a fallu trouver un mécanisme permettant de consommer de la meilleure manière les flux XML envoyés.
Après avoir pensé au début à parser manuellement le XML (solution vite abandonnée), nous avons imaginé ajouter une étape intermédiaire de transformation de la réponse en JSON afin de faciliter le traitement des données par Angular.
Après analyse du volume de données envoyé, il a été jugé raisonnable de gérer complètement les transactions avec l’API et la transformation en JSON dans l’application.
L’API étant hébergée sur un domaine différent de l’application, il a d’abord fallu contourner la restriction du same-origin-policy des navigateurs*, empêchant l’application d’interroger les web services d’un domaine différent du sien en mettant en place un reverse-proxy sur notre serveur Apache.
La librairie x2js permet de gérer la conversion XML vers JSON directement en JavaScript et s’intègre efficacement à une application Angular.
Il est alors très simple de créer un service transformant tout fichier XML en JSON :
Le XML étant un format pouvant avoir de multiples formes et syntaxes, il reste nécessaire de vérifier la manière dont les données ont été transformées et d’ajouter un nettoyage des données personnalisé le cas échéant.
Au delà du temps de latence dû à l’appel à l’API, gérer la conversion du XML vers JSON est coûteux en terme de temps de réception du résultat (en jaune foncé) :
Il est possible de réduire le temps de réponse en mettant en place un proxy :
L’utilisation d’un proxy a pour avantages de :
- déléguer le travail d’interrogation de l’API et la transformation du XML en JSON au proxy
- rendre l’application RESTful et permettre la mise en place d’un système de cache efficace
Ces améliorations permettent de réduire le temps de réponse, avec un temps de réception quasi instantané :
Toutefois cette solution n’a finalement pas été retenue pour notre application : malgré un gain de performance sensible, la mise en place d’un proxy pose des questions de maintenance et d’hébergement qu’il a été jugé trop contraignant dans le cadre d’un prototype.
“Premature optimisation is the root of all evil” aussi la mise en place de ces actions n’est en rien dogmatique.
Elles ont néanmoins permis, pour une difficulté et un coût de réalisation raisonnable d’améliorer le temps de réponse de l’application et d’obtenir un gain indéniable et visible en terme de fluidité, rapidité et confort de navigation pour l’utilisateur.
* le Cross-Origin Resource Sharing est une solution alternative à la restriction de same-origin-policy (réservée aux navigateurs les plus récents)