Amélioration de la planification JS avec isInputPending()

Cette nouvelle API JavaScript peut vous aider à éviter le compromis entre les performances de chargement et la réactivité des entrées.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Le chargement rapide est difficile. Les sites qui utilisent actuellement JavaScript pour afficher leur contenu qu'il faut trouver un compromis entre les performances de charge et l'entrée Réactivité: vous pouvez soit effectuer toutes les tâches requises pour l'affichage simultanément (meilleures performances de chargement, moins bonne réactivité aux entrées). diviser le travail en tâches plus petites afin de rester réactif à de saisie et de peinture (mauvaise performance de chargement, meilleure entrée la réactivité).

Pour éliminer ce compromis, Facebook a proposé et implémenté l'API isInputPending() dans Chromium afin d'améliorer la réactivité sans et le rendement. Suite aux commentaires que nous avons reçus concernant la phase d'évaluation, nous avons modifié la API, et nous avons le plaisir de vous annoncer qu'elle est désormais disponible par défaut dans Chromium. 87!

Compatibilité du navigateur

Navigateurs pris en charge

  • Chrome: 87 <ph type="x-smartling-placeholder">
  • Edge: 87 <ph type="x-smartling-placeholder">
  • Firefox: non compatible. <ph type="x-smartling-placeholder">
  • Safari: non compatible. <ph type="x-smartling-placeholder">

Source

isInputPending() expédiés dans les navigateurs basés sur Chromium à partir de la version 87. Aucun autre navigateur n'a signalé d'intention d'expédier l'API.

Contexte

Dans l'écosystème JS actuel, la plupart des tâches sont effectuées sur un seul thread: le thread principal. Cela fournit un modèle d'exécution robuste aux développeurs, mais l'expérience utilisateur (réactivité en particulier) peuvent être considérablement affectés si le script s'exécute en temps réel. Si la page effectue beaucoup de travail lors du déclenchement d'un événement d'entrée, Par exemple, la page ne gérera l'événement de saisie de clic qu'après cette tâche terminé.

La bonne pratique actuelle consiste à résoudre ce problème en décomposant le code JavaScript en blocs plus petits. Pendant le chargement de la page, de code JavaScript, puis céder le contrôle et transmettre le contrôle au navigateur. La le navigateur peut alors vérifier sa file d'attente d'événements d'entrée et voir s'il y a dont il doit parler à la page. Le navigateur peut ensuite reprendre l'exécution des blocs JavaScript au fur et à mesure de leur ajout. Cela aide, mais cela peut causer d'autres problèmes.

Chaque fois que la page rend le contrôle au navigateur, un certain temps le navigateur doit vérifier sa file d'attente des événements d'entrée, traiter les événements et récupérer Bloc JavaScript. Même si le navigateur répond plus rapidement aux événements, le temps de chargement de la page ralentit. Si nous cédons trop souvent, la page charge trop lentement. Si le rendement est moindre, le navigateur met plus de temps répondre aux événements utilisateur, et les gens sont frustrés. Ce n'est pas amusant.

Diagramme illustrant que lorsque vous exécutez de longues tâches JavaScript, le navigateur a moins de temps pour envoyer des événements.

Chez Facebook, nous voulions voir à quoi ressemblerait si nous trouvions un une nouvelle approche de chargement qui éliminerait ce compromis frustrant. Mer nous avons contacté nos amis de l'équipe Chrome pour isInputPending(). L'API isInputPending() est la première à utiliser le concept de interrompt les entrées utilisateur sur le Web et permet vérifier les entrées sans céder au navigateur.

Un diagramme montrant qu&#39;isInputPending() permet à votre code JS de vérifier s&#39;il y a des entrées utilisateur en attente, sans céder complètement l&#39;exécution au navigateur.

Comme l'API avait suscité un intérêt, nous avons collaboré avec nos collègues de l'équipe Chrome pour implémenter et livrer la fonctionnalité dans Chromium. avec l'aide de Chrome ingénieurs, nous avons obtenu les correctifs après une évaluation de l'origine (qui permet à Chrome de tester les modifications et de recevoir 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 W3C Web Performance Working Group et mis en œuvre des modifications à l'API.

Exemple: un planificateur plus rendement

Supposons que vous ayez une tâche de blocage d'affichage à effectuer pour charger votre page, par exemple la génération d'un balisage à partir de composants, la décomposition de nombres premiers ou il suffit de dessiner une icône de chargement de chargement intéressante. Chacun de ces éléments est divisé en une élément de travail. À l'aide du modèle de planificateur, esquissons 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 permet au navigateur de rester réactif à la saisie (il peut exécuter des gestionnaires d'événements avant la reprise du travail) tout en gérant l'exécution sans interruption. Cependant, nous pourrions être déprogrammés longtemps par d'autres travaux qui souhaite contrôler la boucle d'événements ou obtenir jusqu'à QUANTUM millisecondes supplémentaires de latence des événements.

Ce n'est pas grave, mais pouvons-nous faire mieux ? Bien sûr !

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 appelant navigator.scheduling.isInputPending(), nous pouvons : répondre plus rapidement aux entrées, tout en veillant à ce que le blocage de l'affichage fonctionne s’exécute sans interruption. Si nous ne souhaitons pas nous occuper autre que la saisie (par exemple, peinture) jusqu'à ce que le travail soit terminé, nous pouvons facilement augmenter la longueur de QUANTUM également.

Par défaut, « continu » Les événements ne sont pas renvoyés par isInputPending(). Ces incluent mousemove, pointermove et d'autres. Si vous souhaitez générer un rendement sans problème. En fournissant un objet à isInputPending() avec includeContinuous défini sur true, le tour est joué:

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à ! Les frameworks comme React intègrent la prise en charge de isInputPending() dans leur les principales bibliothèques de planification à l’aide d’une logique similaire. J’espère que cela vous les développeurs qui utilisent ces frameworks peuvent bénéficier de isInputPending(). en coulisse sans avoir à les réécrire de façon significative.

Rendement n'est pas toujours mauvais

Notez que le rendement inférieur n'est pas la solution adaptée à chaque utilisation. . Il existe de nombreuses raisons de rendre le contrôle au navigateur, traiter les événements d'entrée, par exemple pour effectuer le rendu et exécuter d'autres scripts sur la page.

Il existe des cas où le navigateur n'est pas en mesure d'attribuer correctement les attributs en attente des événements d'entrée. en particulier la définition d'extraits et de masques complexes pour plusieurs origines. Les iFrames peuvent signaler des faux négatifs (par exemple, isInputPending() peut renvoyer de façon inattendue "false" lors du ciblage de ces frames). Assurez-vous de céder suffisamment souvent si votre site nécessite des interactions avec des sous-cadres stylisés.

Faites également attention aux autres pages qui partagent une boucle d'événements. Sur des plates-formes telles que comme Chrome pour Android, il est assez courant que plusieurs origines partagent un événement en boucle. isInputPending() ne renvoie jamais true si l'entrée est envoyée à un les frames multi-origines. Par conséquent, les pages en arrière-plan peuvent interférer avec le la réactivité des pages de premier plan. Vous voudrez peut-être réduire, repousser plus souvent lorsqu'elles travaillent 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 blocage des utilisateurs, puis soyez gentil avec les autres membres de la boucle d'événements en et à générer plus fréquemment. Les longues tâches peuvent être dangereuses.

Commentaires

  • Laissez un commentaire sur les spécifications dans le is-input-pending.
  • Contactez @acomminos (l'un des auteurs de la spécification). sur Twitter.

Conclusion

Nous sommes heureux de vous annoncer le lancement de isInputPending() et que les développeurs puissent pour commencer à l'utiliser dès aujourd'hui. C'est la première fois que Facebook crée une API nouvelle API Web et est passé de l'incubation d'idées à la proposition de normes pour dans un navigateur. Nous tenons à remercier toutes les personnes qui nous ont aidés à en arriver là et remercier toutes les personnes qui nous ont aidés à étoffer leur fiche cette idée et de la faire livrer !

Photo principale prise par Will H McMahan sur Unsplash.