IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Pourquoi les navigateurs ralentissent-ils les minuteries JavaScript ?
Par Nolan Lawson

Le , par Nolan Lawson

0PARTAGES

4  0 
Pourquoi les navigateurs ralentissent-ils les minuteries JavaScript ? par Nolan Lawson

Même si vous utilisez JavaScript depuis un certain temps, vous serez peut-être surpris d'apprendre que setTimeout(0) n'est pas vraiment setTimeout(0). Au lieu de cela, il pourrait s'exécuter 4 millisecondes plus tard :

Code : Sélectionner tout
1
2
3
4
5
const start = performance.now()
setTimeout(() => {
  // Likely 4ms
  console.log(performance.now() - start)
}, 0)


Il y a près de dix ans, lorsque je faisais partie de l'équipe Microsoft Edge, on m'a expliqué que les navigateurs faisaient cela pour éviter les « abus ». En d'autres termes, il existe de nombreux sites web qui spamment setTimeout. Pour éviter de vider la batterie de l'utilisateur ou de bloquer l'interactivité, les navigateurs fixent un minimum spécial « bridé » de 4 ms.

Cela explique également pourquoi certains navigateurs augmentent l'étranglement pour les appareils sur batterie (16 ms dans le cas de l'ancien Edge), ou l'étranglent encore plus agressivement pour les onglets en arrière-plan (1 seconde dans Chrome !).

Une question m'a toujours taraudé : si setTimeout était si mal utilisé, pourquoi les navigateurs ont-ils continué à introduire de nouveaux temporisateurs comme setImmediate (RIP), Promises, ou même de nouvelles fantaisies comme scheduler.postTask() ? Si setTimeout devait être affaibli, ces temporisateurs ne subiraient-ils pas le même sort ?

J'ai écrit un long billet sur les timers JavaScript en 2018, mais jusqu'à récemment, je n'avais pas de bonne raison de revenir sur cette question. Puis j'ai travaillé sur fake-indexeddb, qui est une implémentation purement JavaScript de l'API IndexedDB, et cette question a refait surface. Il s'avère qu'IndexedDB souhaite auto-commettre les transactions lorsqu'il n'y a pas de travail en cours dans la boucle d'événements - en d'autres termes, une fois que toutes les micro-tâches sont terminées, mais avant qu'aucune tâche (puis-je dire effrontément « macro-tâches » ?) n'ait commencé.

Pour ce faire, fake-indexeddb utilisait setImmediate dans Node.js (qui partage certaines similitudes avec la version héritée du navigateur) et setTimeout dans le navigateur. Dans Node, setImmediate est en quelque sorte parfait, car il s'exécute après les microtâches mais immédiatement avant toute autre tâche, et sans serrage. Dans le navigateur, cependant, setTimeout est plutôt sous-optimal : dans un benchmark, je voyais Chrome prendre 4,8 secondes pour quelque chose qui ne prenait que 300 millisecondes dans Node (un ralentissement de 16x !).

En regardant le paysage des timers en 2025, il n'était pas évident de savoir quoi choisir. Voici quelques options possibles :

- setImmediate - uniquement supporté par les anciens Edge et IE, donc pas de choix.
- MessageChannel.postMessage - c'est la technique utilisée par afterframe.
- window.postMessage - une bonne idée, mais un peu bancale car elle peut interférer avec d'autres scripts sur la page utilisant la même API. Cette approche est cependant utilisée par la polyfill setImmediate.
- scheduler.postTask - si vous ne lisez pas plus loin, c'est le gagnant. Mais expliquons pourquoi !

Pour comparer ces options, j'ai écrit un rapide benchmark. Quelques points importants à propos de ce benchmark :

1. Vous devez exécuter plusieurs itérations de setTimeout (et de ses amis) pour vraiment comprendre le bridage. Techniquement, selon la spécification HTML, la limitation à 4 ms n'est censée intervenir qu'après qu'un setTimeout a été imbriqué (c'est-à-dire qu'un setTimeout en appelle un autre) 5 fois.

2. Je n'ai pas testé toutes les combinaisons possibles entre 1) batterie et branchement, 2) taux de rafraîchissement du moniteur, 3) onglets d'arrière-plan et d'avant-plan, etc. J'ai une vie, et bien qu'il soit amusant d'enfiler la blouse de laboratoire et de faire quelques expériences, je ne veux pas passer tout mon samedi à le faire.

Quoi qu'il en soit, voici les chiffres (en millisecondes, médiane de 101 itérations, sur un MacBook Pro 16 pouces de 2021) :


Note : ce benchmark a été difficile à écrire ! Lorsque je l'ai écrit pour la première fois, j'ai utilisé Promise.all pour lancer tous les timers simultanément, mais cela semblait aller à l'encontre de l'heuristique d'imbrication de Safari, et faisait que le timer de Firefox se déclenchait de manière incohérente. Désormais, le benchmark exécute chaque minuterie de manière indépendante.

Ne vous souciez pas trop des chiffres précis : l'essentiel est que Chrome et Firefox brident setTimeout à 4ms, et que les trois autres options sont à peu près équivalentes. Dans Safari, il est intéressant de noter que setTimeout est encore plus fortement bridé, et que MessageChannel.postMessage est un peu plus lent que window.postMessage (bien que window.postMessage soit toujours bancal pour les raisons citées plus haut).

Cette expérience a répondu à ma question immédiate : fake-indexeddb devrait utiliser scheduler.postTask (que je préfère pour son ergonomie) et se rabattre sur MessageChannel.postMessage ou window.postMessage. (J'ai expérimenté différentes priorités pour postTask, mais elles ont toutes donné des résultats presque identiques. Pour le cas d'utilisation de fake-indexeddb, la priorité par défaut de 'user-visible' semblait la plus appropriée, et c'est ce que le benchmark utilise).

Mais rien de tout cela ne répond à ma question initiale : pourquoi les navigateurs s'embarrassent-ils de setTimeout si les développeurs web peuvent simplement utiliser scheduler.postTask ou MessageChannel à la place ? J'ai posé la question à mon ami Todd Reifsteck, qui était co-président du groupe de travail sur les performances du web à l'époque où ces discussions sur les « interventions » étaient en cours.

Il m'a répondu qu'il y avait en fait deux camps : l'un estimait que les temporisateurs devaient être limités pour protéger les développeurs Web contre eux-mêmes, tandis que l'autre estimait que les développeurs devaient « mesurer leur propre folie » et que toute heuristique de limitation subtile ne ferait que semer la confusion. En bref, il s'agissait d'un compromis standard dans la conception d'API performantes : « certaines API sont rapides mais sont accompagnées de fusils à pompe ».

Cela correspond à mes propres intuitions sur le sujet. Les interventions du navigateur sont généralement mises en place parce que les développeurs web ont soit utilisé trop d'une bonne chose (par exemple setTimeout), soit ignoré allègrement de meilleures options (la controverse sur le Touch Listener en est un bon exemple). En fin de compte, le navigateur est un « agent utilisateur » qui agit au nom de l'utilisateur, et la priorité des groupes d'intérêt du W3C indique clairement que les besoins de l'utilisateur final l'emportent toujours sur ceux du développeur web.

Cela dit, les développeurs web veulent souvent faire ce qu'il faut. (Nous n'avons simplement pas toujours les outils pour le faire, alors nous saisissons l'instrument contondant qui se trouve à proximité et commençons à frapper. En nous donnant plus de contrôle sur les tâches et la planification, nous pourrions éviter d'avoir à marteler setTimeout et de provoquer un désordre qui nécessite une intervention.

Ma prédiction est que postTask / postMessage resteront pour l'instant sans ralentissement. Parmi les deux « camps » de Todd, l'existence même de l'API Scheduler, qui offre toute une série d'outils de planification des tâches, semble indiquer que le camp « pro-contrôle » est celui qui dirige actuellement le navire. Todd considère l'API comme un compromis entre les deux groupes : oui, elle offre beaucoup de contrôle, mais elle s'aligne également sur le pipeline de rendu du navigateur plutôt que sur des délais aléatoires.

Le pessimiste en moi se demande cependant si l'API ne pourrait pas encore être utilisée de manière abusive, par exemple en utilisant sans précaution la priorité user-blocking partout. Peut-être qu'à l'avenir, un éditeur de navigateur entreprenant mettra plus fermement le pied sur l'accélérateur (pour ainsi dire) et découvrira que cela rend les sites web plus rapides, plus réactifs et moins gourmands en batterie. Dans ce cas, nous pourrions assister à une nouvelle série d'interventions. (Peut-être aurons-nous besoin d'une API scheduler2 pour nous sortir de ce pétrin !)

Je ne suis plus très impliqué dans les normes du web et je ne peux que spéculer. Pour l'instant, je me contenterai de faire ce que font la plupart des développeurs web : choisir l'API qui me permet d'atteindre mes objectifs aujourd'hui, et espérer que les navigateurs ne changeront pas trop à l'avenir. Tant que nous sommes prudents et que nous n'introduisons pas trop de « bêtises », je ne pense pas que ce soit trop demander.

Merci à Todd Reifsteck pour ses commentaires sur une version préliminaire de cet article.

Note : tout ce que j'ai dit à propos de setTimeout pourrait également être dit à propos de setInterval. Du point de vue du navigateur, il s'agit pratiquement des mêmes API.

Note : pour ce que ça vaut, fake-indexeddb utilise toujours setTimeout plutôt que MessageChannel ou window.postMessage dans Safari. Malgré mes benchmarks ci-dessus, je n'ai pu obtenir que window.postMessage surpasse les deux autres dans le propre benchmark de fake-indexeddb - Safari semble avoir un certain étranglement supplémentaire pour MessageChannel que mon benchmark autonome n'a pas pu mettre en évidence. Et window.postMessage me semble toujours sujet à des erreurs, je suis donc réticent à l'utiliser. Pour les curieux, voici mon benchmark.

Source : "Why do browsers throttle JavaScript timers?"

Et vous ?

Pensez-vous que cet avis est crédible ou pertinent ?
Quel est votre avis sur le sujet ?

Voir aussi :

Pourquoi je suis sceptique quant à la réécriture des outils JavaScript dans des langages "plus rapides", par Nolan Lawson

Tout partout en même temps : Les promesses JavaScript, par Mensur Durakovic
Vous avez lu gratuitement 756 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !