Une nouvelle API JavaScript qui peut vous aider à éviter le compromis entre les performances de chargement et la réactivité des entrées.
Il est difficile de charger rapidement. Les sites qui exploitent le code JavaScript pour afficher leur contenu doivent actuellement faire un compromis entre les performances de chargement et la réactivité des entrées: soit effectuer tout le travail nécessaire à l'affichage en une seule fois (meilleures performances de chargement, moins de réactivité des entrées), soit diviser le travail en tâches plus petites afin de rester réactif aux entrées et à la peinture (moins bonnes performances de chargement, meilleure réactivité des entrées).
Pour éviter d'avoir à faire ce compromis, Facebook a proposé et implémenté l'API isInputPending()
dans Chromium afin d'améliorer la réactivité sans céder. Suite aux commentaires que nous avons reçus concernant la phase d'évaluation, nous avons apporté un certain nombre de mises à jour à l'API, et nous sommes heureux d'annoncer qu'elle est désormais disponible par défaut dans Chromium 87.
Compatibilité du navigateur
isInputPending()
expédiés dans les navigateurs basés sur Chromium à partir de la version 87.
Aucun autre navigateur n'a signalé l'intention d'envoyer l'API.
Contexte
La plupart des tâches de l'écosystème JavaScript actuel sont effectuées sur un seul thread: le thread principal. Cela fournit aux développeurs un modèle d'exécution robuste, mais l'expérience utilisateur (la réactivité en particulier) peut être considérablement affectée si le script s'exécute pendant une longue période. Par exemple, si la page effectue de nombreuses tâches lorsqu'un événement d'entrée est déclenché, elle ne gérera pas l'événement d'entrée de clic tant que cette tâche n'est pas terminée.
La bonne pratique actuelle consiste à résoudre ce problème en divisant le code JavaScript en blocs plus petits. Pendant le chargement de la page, celle-ci peut exécuter un extrait de code JavaScript, puis céder le contrôle et transmettre le contrôle au navigateur. Le navigateur peut ensuite vérifier sa file d'attente d'événements d'entrée et voir s'il doit communiquer quelque chose à la page. Le navigateur peut ensuite reprendre l'exécution des blocs JavaScript à mesure qu'ils sont ajoutés. Cela aide, mais cela peut causer d'autres problèmes.
Chaque fois que la page rend le contrôle au navigateur, il faut un certain temps pour que celui-ci vérifie sa file d'attente d'événements d'entrée, traite les événements et récupère le bloc JavaScript suivant. Bien que le navigateur réponde plus rapidement aux événements, le temps de chargement global de la page est ralenti. Et si nous célébrons trop souvent, la page se charge trop lentement. Si nous célébrons moins souvent, le navigateur met plus de temps à répondre aux événements utilisateur, ce qui génère de la frustration. Ce n'est pas amusant.
Chez Facebook, nous voulions voir à quoi ressemblerait une nouvelle approche de chargement qui éliminerait ce compromis frustrant. Nous avons contacté nos amis de Chrome à ce sujet et avons proposé isInputPending()
. L'API isInputPending()
est la première à utiliser le concept d'interruptions pour les entrées utilisateur sur le Web et permet à JavaScript de vérifier les entrées sans céder au navigateur.
L'API ayant suscité l'intérêt, nous avons collaboré avec nos collègues de Chrome pour implémenter et déployer la fonctionnalité dans Chromium. Avec l'aide des ingénieurs Chrome, nous avons déployé les correctifs dans le cadre d'un essai d'origine (qui permet à Chrome de tester les modifications et de recueillir les commentaires des développeurs avant de publier complètement une API).
Nous avons pris en compte les commentaires de la phase d'évaluation et des autres membres du groupe de travail du W3C sur les performances Web, et avons apporté des modifications à l'API.
Exemple: un planificateur de yieldier
Supposons que vous deviez effectuer de nombreuses tâches bloquant l'affichage pour charger votre page, par exemple générer du balisage à partir de composants, factoriser des nombres premiers ou simplement dessiner un voyant de chargement sympa. Chacun d'eux est décomposé en éléments de travail distincts. À l'aide du modèle de planificateur, décrivons comment nous pourrions traiter notre travail dans une fonction processWorkQueue()
hypothétique:
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (performance.now() >= DEADLINE) {
// Yield the event loop if we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
En appelant processWorkQueue()
plus tard dans une nouvelle macrotâche via setTimeout()
, nous permettons au navigateur de rester quelque peu réactif aux entrées (il peut exécuter des gestionnaires d'événements avant la reprise du travail) tout en parvenant à s'exécuter de manière relativement ininterrompue. Toutefois, nous pouvons être désorganisés pendant une longue période par d'autres tâches qui souhaitent contrôler la boucle d'événements ou atteindre une latence d'événement supplémentaire de QUANTUM
millisecondes.
C'est bien, mais pouvons-nous faire mieux ? Tout à fait !
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event, or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
En introduisant un appel à navigator.scheduling.isInputPending()
, nous pouvons répondre plus rapidement aux entrées tout en nous assurant que notre travail de blocage de l'écran s'exécute sans interruption. Si nous ne souhaitons gérer que l'entrée (par exemple, la peinture) avant la fin du travail, nous pouvons également augmenter facilement la longueur de QUANTUM
.
Par défaut, les événements "continus" ne sont pas renvoyés à partir de isInputPending()
. Cela inclut mousemove
, pointermove
et d'autres. Si vous souhaitez également générer des revenus pour ces éléments, ce n'est pas un problème. En fournissant un objet à isInputPending()
avec includeContinuous
défini sur true
, vous pouvez continuer:
const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event (any of them!), or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
Et voilà ! Des frameworks tels que React intègrent la prise en charge de isInputPending()
dans leurs bibliothèques de planification principales à l'aide d'une logique similaire. Nous espérons que cela permettra aux développeurs qui utilisent ces frameworks de bénéficier de isInputPending()
en coulisses sans réécriture importante.
La cession n'est pas toujours mauvaise
Il convient de noter que réduire la production n'est pas la solution idéale pour tous les cas d'utilisation. Il existe de nombreuses raisons de rendre le contrôle au navigateur, sauf pour traiter les événements d'entrée, par exemple pour effectuer l'affichage et exécuter d'autres scripts sur la page.
Il arrive que le navigateur ne parvienne pas à attribuer correctement les événements d'entrée en attente. En particulier, le paramétrage de clips et de masques complexes pour les iFrames inter-origines peut générer des faux négatifs (c'est-à-dire que isInputPending()
peut renvoyer de manière inattendue la valeur "false" lors du ciblage de ces cadres). Assurez-vous de générer des résultats suffisamment souvent si votre site nécessite des interactions avec des sous-cadres stylisés.
Tenez également compte des autres pages qui partagent une boucle d'événements. Sur des plates-formes telles que Chrome pour Android, il est assez courant que plusieurs origines partagent une boucle d'événements. isInputPending()
ne renverra jamais true
si la saisie est distribuée à un frame inter-origines. Par conséquent, les pages en arrière-plan peuvent interférer avec la réactivité des pages au premier plan. Vous souhaiterez peut-être réduire, repousser ou céder le rendement plus souvent lorsque vous travaillez en arrière-plan à l'aide de l'API Page Visibility.
Nous vous encourageons à utiliser isInputPending()
avec discernement. S'il n'y a pas de travail bloquant les utilisateurs à effectuer, soyez gentil avec les autres dans la boucle d'événements en générant un rendement plus fréquemment. Les tâches longues peuvent être dangereuses.
Commentaires
- Envoyez vos commentaires sur la spécification dans le dépôt is-input-pending.
- Contactez @acomminos (l'un des auteurs de la spécification) sur Twitter.
Conclusion
Nous sommes heureux du lancement de isInputPending()
et que les développeurs puissent commencer à l'utiliser dès aujourd'hui. Avec cette API, Facebook a créé une nouvelle API Web pour la faire passer de l'incubation d'idées à la proposition de normes, puis à la mise en service dans un navigateur. Nous tenons à remercier toutes les personnes qui nous ont aidés à en arriver là. Merci à tous les employés de Chrome qui nous ont aidés à élaborer cette idée et à la faire expédier.
Photo d'illustration par Will H McMahan sur Unsplash.