Introduzione alla prova dell'origine scheduler.yield

Creare siti web che rispondono rapidamente all'input degli utenti è stato uno degli aspetti più impegnativi delle prestazioni web e che il team di Chrome si sta impegnando duramente per aiutare gli sviluppatori web a soddisfare le loro esigenze. Proprio quest'anno è stato annunciato che la metrica Interaction to Next Paint (INP) sarebbe passata dallo stato sperimentale a quello In attesa. A marzo 2024, è destinato a sostituire First Input Delay (FID) come Segnale web essenziale.

Nell'ambito del continuo impegno per fornire nuove API che aiutano gli sviluppatori web a rendere i loro siti web più accattivanti, il team di Chrome sta attualmente eseguendo una prova delle origini per scheduler.yield a partire dalla versione 115 di Chrome. scheduler.yield è una nuova aggiunta all'API scheduler che offre un modo migliore e più semplice per restituire il controllo del thread principale rispetto ai metodi tradizionalmente utilizzati.

Alla consegna

JavaScript utilizza il modello da esecuzione al completamento per gestire le attività. Ciò significa che, quando un'attività viene eseguita sul thread principale, viene eseguita per il tempo necessario al completamento. Al termine di un'attività, il controllo viene restituito al thread principale, in modo che quest'ultimo possa elaborare l'attività successiva in coda.

A parte i casi estremi in cui un'attività non termina mai, ad esempio un ciclo infinito, il rendimento è un aspetto inevitabile della logica di pianificazione delle attività di JavaScript. accadrà, è solo una questione di quando e il prima possibile è meglio di quando. Quando l'esecuzione delle attività richiede troppo tempo, per l'esattezza superiore a 50 millisecondi, vengono considerate attività lunghe.

Le attività lunghe causano una scarsa adattabilità delle pagine perché ritardano la capacità del browser di rispondere all'input dell'utente. Più spesso si verificano attività lunghe e più a lungo vengono eseguite, più è probabile che gli utenti abbiano l'impressione che la pagina sia lenta o addirittura che non sia funzionante.

Tuttavia, il semplice fatto che il codice avvii un'attività nel browser non significa che devi attendere fino al termine di quell'attività prima che il controllo venga riportato al thread principale. Puoi migliorare la reattività all'input dell'utente in una pagina partecipando esplicitamente a un'attività, interrompendo così l'attività alla successiva occasione disponibile. In questo modo, le altre attività ottengono il tempo necessario per il thread principale prima del completamento di un'attività molto lunga.

Una rappresentazione di come suddividere un'attività può favorire una migliore reattività all'input. In alto, un'attività lunga impedisce l'esecuzione di un gestore di eventi fino al completamento dell'attività. In fondo, l'attività in blocchi consente al gestore di eventi di essere eseguita prima di quanto sarebbe altrimenti.
Una visualizzazione del controllo che restituisce al thread principale. In alto, la restituzione avviene solo dopo l'esecuzione fino al completamento di un'attività, il che significa che il completamento delle attività può richiedere più tempo prima di restituire il controllo al thread principale. In fondo, la produzione avviene esplicitamente, suddividendo un'attività lunga in diverse attività più piccole. In questo modo, le interazioni degli utenti vengono eseguite prima, il che migliora la reattività dell'input e l'INP.

Quando rispondi esplicitamente, dici al browser "Ehi, so che il lavoro che sto per fare potrebbe richiedere del tempo e non voglio che tu debba fare tutto prima di rispondere all'input degli utenti o ad altre attività che potrebbero essere importanti". Si tratta di uno strumento prezioso nella gamma di strumenti di uno sviluppatore che può fare molto per migliorare l'esperienza utente.

Il problema delle attuali strategie di rendimento

Un metodo comune per restituire usa setTimeout con un valore di timeout pari a 0. Questo comando funziona perché il callback passato a setTimeout sposterà il lavoro rimanente in un'attività separata che verrà messa in coda per la successiva esecuzione. Anziché aspettare che il browser produca da solo, puoi dire "divulghiamo questa grossa parte di lavoro in parti più piccole".

Tuttavia, la generazione di setTimeout comporta un effetto collaterale potenzialmente indesiderato: il lavoro che si verifica dopo il punto di rendimento verrà spostato in fondo alla coda delle attività. Le attività pianificate dalle interazioni degli utenti continueranno a essere in primo piano come dovrebbero, ma il lavoro rimanente che volevi fare dopo l'esplicita cedimento potrebbe essere ulteriormente ritardato da altre attività provenienti da origini concorrenti che erano in coda prima di questa.

Per una dimostrazione di questa funzionalità, prova questa demo di Glitch o sperimenta la funzionalità nella versione incorporata di seguito. La demo è composta da alcuni pulsanti su cui puoi fare clic e da una casella sottostante che registra quando vengono eseguite le attività. Quando giudichi una pagina, esegui le seguenti azioni:

  1. Fai clic sul pulsante in alto Esegui attività periodicamente, che pianifica l'esecuzione delle attività di blocco di tanto in tanto. Quando fai clic su questo pulsante, il log delle attività viene compilato con diversi messaggi che riportano la dicitura Attività di blocco con setInterval.
  2. Quindi, fai clic sul pulsante Esegui loop, restituendo con setTimeout a ogni iterazione.

Noterai che la casella nella parte inferiore della demo avrà il seguente aspetto:

Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval

Questo output mostra il comportamento di "fine della coda di attività" che si verifica quando generi con setTimeout. Il loop che viene eseguito elabora cinque elementi e restituisce setTimeout dopo che ciascuno è stato elaborato.

Questo evidenzia un problema comune sul web: non è insolito che uno script (soprattutto uno script di terze parti) registri una funzione timer che esegue un lavoro a intervalli. Il comportamento di "fine della coda delle attività" associato alla generazione di setTimeout significa che il lavoro di altre origini delle attività potrebbe essere inserito in coda prima del lavoro rimanente che il loop deve svolgere dopo la consegna.

A seconda della tua applicazione, questo potrebbe essere o meno un risultato auspicabile, ma in molti casi questo è il motivo per cui gli sviluppatori potrebbero sentirsi riluttanti a rinunciare così prontamente al controllo del thread principale. La produttività è un buon risultato perché le interazioni degli utenti hanno l'opportunità di essere eseguite prima, ma permette anche ad altre attività di interazione non con l'utente di ottenere tempo sul thread principale. È un problema reale, ma scheduler.yield può aiutarti a risolverlo.

Entra in: scheduler.yield

scheduler.yield è protetto da un flag come funzionalità sperimentale della piattaforma web sin dalla versione 115 di Chrome. Una domanda che potresti chiederti è: "Perché ho bisogno di una funzione speciale per generare quando setTimeout la fa già?".

Vale la pena notare che il rendimento non era un obiettivo di progettazione di setTimeout, ma piuttosto un buon effetto collaterale nella pianificazione di un callback da eseguire in un momento futuro, anche se è stato specificato un valore di timeout pari a 0. Tuttavia, è più importante ricordare che la generazione con setTimeout invia il lavoro rimanente alla parte posteriore della coda di attività. Per impostazione predefinita, scheduler.yield invia il lavoro rimanente in inizio della coda. Ciò significa che il lavoro che vuoi riprendere subito dopo aver ceduto non passerà in secondo piano alle attività provenienti da altre origini (ad eccezione delle interazioni degli utenti).

scheduler.yield è una funzione che cede al thread principale e restituisce Promise quando viene richiamata. Ciò significa che puoi await in una funzione async:

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

Per vedere scheduler.yield in azione, procedi nel seguente modo:

  1. Vai a chrome://flags.
  2. Attiva l'esperimento Funzionalità sperimentali della piattaforma web. Al termine dell'operazione, potrebbe essere necessario riavviare Chrome.
  3. Vai alla pagina demo o utilizza la versione incorporata sotto questo elenco.
  4. Fai clic sul pulsante in alto con l'etichetta Esegui attività periodicamente.
  5. Infine, fai clic sul pulsante Esegui loop, restituendo con scheduler.yield a ogni iterazione.

L'output nella casella nella parte inferiore della pagina sarà simile a questo:

Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval

A differenza della demo che restituisce usando setTimeout, puoi vedere che il loop, anche se restituisce dopo ogni iterazione, non invia il lavoro rimanente in fondo alla coda, ma piuttosto in primo piano. Questo ti offre il meglio di entrambi i mondi: puoi cedere per migliorare la reattività degli input sul tuo sito web, ma anche assicurarti che il lavoro che volevi portare a termine dopo non subisca ritardi.

Prova anche tu!

Se scheduler.yield ti sembra interessante e vuoi provarlo, puoi farlo in due modi a partire dalla versione 115 di Chrome:

  1. Se vuoi sperimentare con scheduler.yield in locale, digita e inserisci chrome://flags nella barra degli indirizzi di Chrome, poi seleziona Attiva dal menu a discesa nella sezione Funzionalità sperimentali della piattaforma web. scheduler.yield (e tutte le altre funzionalità sperimentali) saranno disponibili solo nella tua istanza di Chrome.
  2. Se vuoi abilitare scheduler.yield per utenti Chromium reali su un'origine accessibile pubblicamente, dovrai registrarti per la prova dell'origine di scheduler.yield. In questo modo puoi sperimentare in sicurezza le funzionalità proposte per un determinato periodo di tempo e il team di Chrome offre informazioni preziose su come queste funzionalità vengono utilizzate sul campo. Per saperne di più su come funzionano le prove dell'origine, leggi questa guida.

La modalità di utilizzo di scheduler.yield, pur supportando i browser che non la implementano, dipende dai tuoi obiettivi. Puoi utilizzare il polyfill ufficiale. Il polyfill è utile se si applica la tua situazione:

  1. Utilizzi già scheduler.postTask nella tua applicazione per pianificare le attività.
  2. Vuoi essere in grado di impostare l'attività e le priorità.
  3. Vuoi essere in grado di annullare o modificare la priorità delle attività tramite il corso TaskController offerto dall'API scheduler.postTask.

Se questa non è la tua situazione, il polyfill potrebbe non fare al caso tuo. In tal caso, puoi eseguire il rollback di tua proprietà in un paio di modi. Il primo approccio utilizza scheduler.yield se è disponibile, ma torna a setTimeout in caso contrario:

// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

Questa operazione può funzionare, ma, come puoi immaginare, i browser che non supportano scheduler.yield genereranno un comportamento "prima della coda". Se questo significa che preferisci non cedere affatto, puoi provare un altro approccio che utilizzi scheduler.yield se è disponibile, ma che non genera alcun rendimento se non lo è:

// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to nothing:
  return;
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

scheduler.yield è un'aggiunta interessante all'API scheduler che, si spera, consentirà agli sviluppatori di migliorare più facilmente la reattività rispetto alle attuali strategie di rendimento. Se scheduler.yield ti sembra un'API utile, partecipa alla nostra ricerca per aiutarci a migliorarla e invia un feedback su come potrebbe essere ulteriormente migliorata.

Immagine hero di Unsplash, di Jonathan Allison.