publié par

Frédéric Pineau
Directeur Technique

Technique
Lecture 7 minutes

JS, cédez le passage ! Ordonnanceur JavaScript

L’ intégration L'intégration consiste à assembler des éléments visuels (HTML, CSS, JavaScript) pour transformer des maquettes graphiques en pages web fonctionnelles et interactives, tout en respectant les standards du web. des Web Vitals dans l’approche du positionnement du moteur de recherche de Google soulève beaucoup de questions. Parmi les 3 indicateurs en question, le FID Le First Input Delay (FID) est une métrique qui mesure le temps écoulé entre la première interaction de l'utilisateur avec une page web (clic, touche) et la réponse du navigateur, reflétant la réactivité du site. concentre toutes les attentions car il est plus complexe à mesurer, interpréter et améliorer. Alors même que le TBT est plus simple à appréhender pour améliorer le FID, il existe des techniques pour répondre plus rapidement à une interaction de l’utilisateur en utilisant un système de queue ou autrement nommé un ordonnanceur JavaScript JavaScript est un langage de programmation dynamique principalement utilisé pour ajouter des fonctionnalités interactives aux pages web. Il permet de manipuler le DOM, de gérer des événements, et d'effectuer des requêtes asynchrones. .

Un seul fil d’exécution : le thread principal

La plupart des tâches exécutées en JavaScript sont effectuées sur un seul thread Un thread (ou fil d'exécution) est une unité de traitement au sein d'un programme qui permet d'exécuter plusieurs tâches en parallèle, améliorant ainsi l'efficacité et la réactivité des applications. : le thread principal. Cela permet aux développeurs d’avoir un modèle d’exécution robuste mais au détriment de l’ expérience utilisateur L'expérience utilisateur (UX) désigne la qualité de l'interaction d'un utilisateur avec un produit ou service, en termes de satisfaction, facilité d'utilisation et efficacité. . En effet, si la page web effectue beaucoup de tâches JavaScript au chargement de la page, alors qu’une interaction utilisateur est déclenchée via un évènement, au clic par exemple ; la page ne gérera pas l’événement du clic tant que les tâches précédentes ne seront pas terminées.

Les tâches longues en javascript bloquent les interactions utilisateurs.
Figure 1. TBT et fin d’exécution des scripts à 240ms pour un FID de 200ms et un SID (Second Input Delay) de 100ms.

Le temps cumulé durant lequel une page est dans l’incapacité de répondre efficacement à une interaction utilisateur est justement le fameux Total Blocking Time Le Total Blocking Time (TBT) est une métrique de performance web qui mesure le temps pendant lequel une page web reste bloquée et ne répond pas aux interactions utilisateur, de l'apparition du premier contenu jusqu'à l'interactivité complète. (TBT). Pour être précis ce TBT est calculé à partir de la somme des temps alloués aux tâches longues correspondant à un temps de plus de 50 millisecondes. Or, n’oubliez pas l’objectif : réduire le FID qui selon les recommandations de Google doit être en dessous de 100 millisecondes.

Des tâches plus courtes et séparées par des intervalles

Découpage des tâches longues et petites tâches d'une durée inférieure à 50ms.
Figure 2. Fin d’exécution des scripts à 240ms pour un FID de 200ms et un SID de 100ms.

Pour résoudre le problème, le premier réflexe est de diviser le JavaScript en blocs plus petits (cf figure 2), et de les exécuter à intervalle régulier (avec un setTimeout() par exemple) pour qu’à chaque intervalle le navigateur puisse répondre aux interactions (cf figure 3).

Créer un intervalle de temps pour les interactions utilisateurs entre chaque tache javascript.
Figure 3. Fin d’exécution des scripts à 350ms pour un FID de 30ms et un SID de 10ms

NB : même si le setTimeout()est à 0ms, il faut un certain temps au navigateur pour vérifier sa file d’attente d’interaction, traiter les événements et sélectionner la tâche JavaScript suivante. D’où l’espace de temps entre chaque tâche JavaScript (cf figure 3  : le temps de 10ms alloué à cet intervalle sur ce schéma est une valeur à titre d’exemple. Cet intervalle diffère selon l’appareil et le navigateur). Dès lors, le navigateur répond plus rapidement aux événements, mais le temps de chargement global de la page est ralenti.

Créer un ordonnanceur JavaScript

Nate Schloss et Andrew Comminos sur web.dev nous décrivent un principe d’ordonnanceur JavaScript. Le principe est très astucieux… Au lieu d’appeler les tâches les unes derrières les autres, un ordonnanceur va les répertorier dans une fonction « todolist ».

Celle-ci va exécuter les différentes tâches dans l’ordre et va effectuer un test avant de lancer la tâche suivante de la todolist. En effet, la fonction va vérifier qu’un certain délai n’est pas encore écoulé :

  • Si non il lance la tâche suivante.
  • Si oui, il arrête l’exécution et se relance avec un setTimeout.
Ordonnanceur de tache javascript avec une interruption selon un délais
Figure 4. Fin d’exécution des scripts à 290ms pour un FID de 20ms et un SID de 60ms 

En s’interrompant ainsi, l’ordonnanceur cède la place à une éventuelle interaction utilisateur. Et comme le setTimeout() ne défini pas de timing, l’ordonnanceur reprendra immédiatement après cette interaction avec la tâche suivante de sa todolist et avec le même délai. C’est d’autant plus malin si on définit un délai inférieur ou égal à 50ms.

En terme de code cela donne ceci :

function todoList() {
   const DEADLINE = performance.now() + DELAY;
   while (workQueue.length > 0) {
      if (performance.now() >= DEADLINE) {
         setTimeout(todoList);
         return;
      }
      let job = workQueue.shift();
      job.execute();
   }
}

Mais cela pourrait être encore mieux. En effet, avec cette solution le SID (Second Input Delay qui n’est absolument pas une abréviation courante mais créée ici pour les besoins de la démonstration) est un peu plus long en figure 4 que ce qu’il pourrait être…

Une nouvelle API JavaScript : isInputPending()

En effet, le test entre chaque tâche de la todolist pourrait être beaucoup plus pertinent si au lieu de tester un délais il testait l’existence réel d’une interaction en attente.

Ordonnanceur de tache javascript avec une interruption selon l'existence d'une interaction utilisateur
Figure 4. Fin d’exécution des scripts à 280ms pour un FID de 20ms et un SID de 10ms 

C’est donc ce que permet cette nouvelle api Une API (Application Programming Interface) est un ensemble de règles permettant à différents logiciels de communiquer entre eux. Elle simplifie l'intégration et l'échange de données entre systèmes. isInputAppend() proposée par les développeurs de Facebook qui a été introduite dans la version 87 de Google Chrome disponible depuis le mois dernier ! A noter que isInputAppend() est autant disponible pour événement court comme le « clic » que pour les évènements longs comme le « move ».

Ce qui change le code de notre ordonnanceur JavaScript comme ceci :

function todoList() {
   while (workQueue.length > 0) {
      if ( navigator.scheduling.isInputPending() ) {
         setTimeout(todoList);
         return;
      }
      let job = workQueue.shift();
      job.execute();
   }
}

Application de l’ordonnanceur JavaScript

Attention, n’en déplaise à Mel Gibson, ce n’est pas une arme absolue. Tout d’abord car l’api isInputAppend() n’est pas disponible dans tous les navigateurs. Il conviendra dès lors d’hybrider notre fonction pour cumuler un test avec un délais et l’api isInputAppend(). Ce qui donnerait ceci :

function todoList() {
   const DEADLINE = performance.now() + DELAY;
   while (workQueue.length > 0) {
      if (performance.now() >= DEADLINE || navigator.scheduling.isInputPending() ) {
         setTimeout(todoList);
         return;
      }
      let job = workQueue.shift();
      job.execute();
   }
}

Le résultat est un peu moins rapide qu’avec un seul test d’interaction utilisateur mais reste correct.

Ordonnanceur de tache javascript avec une interruption selon un délais ou l'existence d'une interaction utilisateur
Figure 4. Fin d’exécution des scripts à 300ms pour un FID de 20ms et un SID de 20ms

Cette hybridation présente un autre intérêt. Elle laisse de la place à d’autres scripts qui pourraient provenir de services tiers via des requêtes asynchrones ou des iframes. N’oubliez jamais que les tâches longues nuisent à l’expérience utilisateur.

Ensuite, tout dépend du fonctionnel attendu. On peut imaginer une première série de tâches qui ne peuvent pas être interrompues puis une autre série qui passerait par ce type d’ordonnanceur. Il faudra bien juger de la pertinence de cet ordonnanceur selon ce fonctionnel.

D’ailleurs, Nate Schloss et Andrew Comminos sont assez clairs sur le sujet :

Nous vous encourageons
à utiliser isInputPending()
avec discernement.

Notamment car son usage s’avère plus subtil qu’il en a l’air. Nous vous invitons donc à lire l’article sur web.dev où nous avons découvert ces principes. Je ne doute pas que les amateurs de Web Performance que je connais, ont déjà leurs neurones en pleine réflexion en découvrant ce concept ingénieux.

Articles similaires