Back

Tâches d'arrière-plan dans le navigateur avec l'API Scheduler

Tâches d'arrière-plan dans le navigateur avec l'API Scheduler

Votre application semble rapide… jusqu’à ce qu’elle ne le soit plus. Un utilisateur clique sur un bouton, et rien ne se passe pendant 300 millisecondes parce que le thread principal est occupé à traiter quelque chose qui n’avait pas besoin de l’être à ce moment-là. C’est précisément le problème que l’API Prioritized Task Scheduling — communément appelée API Scheduler — est conçue pour résoudre.

Cet article explique comment scheduler.postTask() et scheduler.yield() vous offrent un contrôle pratique sur la planification du thread principal, quand les utiliser plutôt que des approches plus anciennes, et quel est l’état actuel de la prise en charge par les navigateurs.

Points clés à retenir

  • L’API Scheduler vous offre un contrôle fin sur le moment et la manière dont s’exécute le travail du thread principal, avec trois niveaux de priorité : user-blocking, user-visible et background.
  • scheduler.postTask() diffère le travail avec une priorité explicite, tandis que scheduler.yield() fractionne les tâches longues afin que le navigateur puisse traiter les entrées utilisateur entre les étapes.
  • Aucune de ces méthodes ne déplace le travail hors du thread principal. Pour un véritable parallélisme, utilisez les Web Workers.
  • Les navigateurs Chromium et Firefox prennent en charge l’API ; Safari ne le fait pas, donc incluez toujours un repli avec setTimeout.
  • Une planification plus intelligente peut améliorer l’Interaction to Next Paint (INP) ainsi que la réactivité globale.

Pourquoi la planification du thread principal est importante

JavaScript s’exécute sur un seul thread. Chaque exécution de script, mise à jour du DOM et gestionnaire d’événements se disputent la même ressource. Lorsqu’une tâche longue bloque ce thread, le navigateur ne peut pas répondre aux entrées utilisateur — ce qui nuit directement à votre score d’Interaction to Next Paint (INP).

Les solutions de contournement traditionnelles ont chacune leurs limites :

  • setTimeout(fn, 0) diffère le travail mais ne vous donne aucun contrôle sur la priorité. Il s’exécute indépendamment de la charge du thread.
  • requestIdleCallback() attend les moments d’inactivité, ce qui est utile pour les travaux de faible priorité, mais il n’a pas de système de priorité, la prise en charge dans Safari est limitée, et il peut être retardé indéfiniment en cas de forte charge.

L’API Scheduler répond à ces deux problèmes.

Fonctionnement de scheduler.postTask()

scheduler.postTask() est le point d’entrée principal de l’API Prioritized Task Scheduling. Il planifie l’exécution d’un callback sur le thread principal à un niveau de priorité spécifié.

scheduler.postTask(() => {
  sendAnalyticsEvent('page_view');
}, { priority: 'background' });

Il existe trois niveaux de priorité :

  • user-blocking — la priorité la plus élevée, pour le travail qui bloque directement une interaction utilisateur
  • user-visible — la valeur par défaut, pour le travail qui affecte ce que voit l’utilisateur sans être bloquant
  • background — la priorité la plus basse, pour l’analytique, le préchargement ou le nettoyage

C’est la différence essentielle avec les anciennes API : vous ne faites pas que reporter le travail, vous indiquez au navigateur à quel point il est important par rapport à tout le reste dans la file d’attente.

scheduler.postTask() retourne une Promise, ce qui facilite son utilisation avec async/await et la gestion propre des erreurs :

try {
  await scheduler.postTask(() => processLargeDataset(data), {
    priority: 'background'
  });
} catch (err) {
  // Task was aborted or failed
  console.warn('Task did not complete:', err);
}

Fractionner les tâches longues avec scheduler.yield()

scheduler.yield() est un ajout plus récent qui résout un problème différent : que faire lorsque vous êtes déjà à l’intérieur d’une tâche longue et que vous devez donner au navigateur l’occasion de traiter les entrées avant de poursuivre ?

async function processItems(items) {
  for (const item of items) {
    processItem(item);
    await scheduler.yield(); // yield back to the browser between items
  }
}

Chaque await scheduler.yield() crée un point de contrôle où le navigateur peut traiter les interactions utilisateur en attente avant de reprendre votre boucle. Par défaut, la reprise après yield() est planifiée avec la priorité user-visible, bien qu’elle puisse hériter de la priorité d’un appel postTask() englobant. C’est l’un des outils les plus pratiques pour réduire les tâches longues sans restructurer l’intégralité de votre base de code.

Une clarification importante

Ni scheduler.postTask() ni scheduler.yield() ne déplacent le travail hors du thread principal. Les tâches planifiées de cette manière s’exécutent toujours sur le thread principal — elles sont simplement mises en file d’attente et priorisées plus intelligemment. Si vous avez besoin d’une véritable exécution parallèle, c’est à cela que servent les Web Workers.

Prise en charge par les navigateurs

La prise en charge de l’API Scheduler est solide dans les navigateurs basés sur Chromium (Chrome, Edge, Opera). La prise en charge par Firefox est arrivée bien après celle de Chromium, et Safari ne prend toujours pas en charge l’API à l’heure actuelle. Vous pouvez vérifier la matrice de prise en charge actuelle sur Can I Use.

Une vérification de fonctionnalité minimale avant utilisation :

if ('scheduler' in window && 'postTask' in scheduler) {
  scheduler.postTask(myTask, { priority: 'background' });
} else {
  // Fallback for Safari and older browsers
  setTimeout(myTask, 0);
}

Pour scheduler.yield() en particulier, vérifiez la prise en charge séparément avant de vous y fier en production, car elle a été livrée plus tard que postTask() et bénéficie d’une couverture plus restreinte :

async function safeYield() {
  if ('scheduler' in window && 'yield' in scheduler) {
    await scheduler.yield();
  } else {
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

Quand recourir à l’API Scheduler

Utilisez scheduler.postTask() lorsque vous devez différer un travail non critique — analytique, préchargement ou traitement post-rendu — avec un contrôle de priorité explicite. Utilisez scheduler.yield() lorsque vous avez une boucle ou un processus en plusieurs étapes qui risque de bloquer le traitement des entrées.

Si vos utilisateurs sont principalement sur Chromium ou Firefox, l’API Scheduler est utilisable dès aujourd’hui en production, à condition d’avoir mis en place une détection de fonctionnalités et des solutions de repli. Pour une couverture plus large, conservez le repli setTimeout jusqu’à ce que Safari rattrape son retard.

Conclusion

L’API Scheduler comble une lacune de longue date dans la façon dont la plateforme web gère le travail du thread principal. Au lieu de vous appuyer sur des outils rudimentaires comme setTimeout(fn, 0) ou le requestIdleCallback() à la prise en charge inégale, vous pouvez désormais exprimer une intention : cette tâche est critique, celle-là peut attendre, et cette boucle doit s’interrompre pour les entrées utilisateur. Le résultat peut être des interactions plus fluides et de meilleurs scores INP, avec relativement peu de changements de code. Associez-la à une vérification de fonctionnalité et à un repli judicieux, et vous pouvez l’adopter en toute sécurité dès aujourd’hui.

FAQ

Non. Toutes les tâches planifiées avec scheduler.postTask() s'exécutent toujours sur le thread principal. L'API ne fait que modifier le moment et l'ordre d'exécution des tâches en leur attribuant des priorités. Si vous avez besoin d'un véritable parallélisme pour des travaux gourmands en CPU, utilisez plutôt les Web Workers, qui exécutent le code sur un thread séparé et communiquent avec le thread principal via des messages.

Utilisez scheduler.yield() à l'intérieur d'une fonction ou d'une boucle existante lorsque vous souhaitez faire une brève pause pour que le navigateur puisse traiter les entrées utilisateur, puis reprendre. Utilisez scheduler.postTask() lorsque vous souhaitez planifier une unité de travail discrète à exécuter ultérieurement avec une priorité spécifique. Elles résolvent des problèmes liés mais distincts : céder la main au milieu d'une tâche par rapport à la mise en file de nouvelles tâches.

L'INP mesure la rapidité avec laquelle votre page répond aux interactions utilisateur. Les tâches longues sur le thread principal sont la cause la plus courante d'un mauvais INP. En cédant la main entre les étapes et en attribuant une priorité plus faible au travail non critique, l'API Scheduler aide à maintenir le thread principal disponible pour traiter rapidement les clics, les touches tactiles et les frappes au clavier, ce qui peut réduire la latence des interactions.

Oui, à condition d'inclure une vérification de fonctionnalité et un repli. Un schéma simple consiste à tester scheduler.postTask et à se rabattre sur setTimeout lorsqu'il n'est pas disponible. Cela permet à votre code de fonctionner dans Safari et les navigateurs plus anciens tout en laissant les utilisateurs de Chromium et de Firefox bénéficier de la planification priorisée. Des polyfills existent mais sont généralement inutiles pour une utilisation basique.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay