Introduzione alla prova dell'origine scheduler.yield

La creazione di siti web che rispondano rapidamente all'input degli utenti è stato uno degli aspetti più impegnativi delle prestazioni web, e il team di Chrome si è impegnato a fondo per aiutare gli sviluppatori web a venire incontro. Proprio quest'anno è stato annunciato che la metrica Interazione con Next Paint (INP) sarebbe passata da sperimentale a "in attesa". Ora è in procinto di sostituire First Input Delay (FID) come Core Web Vital a marzo 2024.

Nell'ambito del costante impegno a fornire nuove API che aiutino gli sviluppatori web a rendere i loro siti web il più possibile rapidi, il team di Chrome sta attualmente eseguendo una prova dell'origine per scheduler.yield a partire dalla versione 115 di Chrome. scheduler.yield è una nuova proposta aggiunta all'API scheduler che consente un modo più semplice e migliore per restituire il controllo al thread principale rispetto ai metodi tradizionalmente utilizzati.

Sul cedere

JavaScript usa il modello dall'esecuzione al completamento per gestire le attività. Ciò significa che, quando un'attività viene eseguita sul thread principale, per il tempo necessario al suo completamento. Al completamento di un'attività, il controllo viene restituito al thread principale, consentendo al thread principale di elaborare l'attività successiva nella 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. Succede, si tratta solo di quando e prima è meglio di dopo. Quando l'esecuzione delle attività richiede troppo tempo (oltre i 50 millisecondi per essere precisi), vengono considerate attività lunghe.

Le attività lunghe sono una fonte di scarsa reattività 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 fiacca o persino che pensino che sia completamente interrotta.

Tuttavia, il semplice fatto che il codice avvia un'attività nel browser non significa che devi attendere il completamento dell'attività prima che il controllo venga restituito al thread principale. Puoi migliorare la reattività all'input dell'utente su una pagina cedendo esplicitamente l'attività in un'attività. In questo modo, l'attività viene suddivisa per completarla alla successiva opportunità disponibile. In questo modo le altre attività hanno tempo sul thread principale in anticipo rispetto al tempo necessario attendere il completamento di attività lunghe.

Una rappresentazione di come suddividere un'attività può facilitare una migliore reattività dell'input. In alto, un'attività lunga impedisce l'esecuzione di un gestore di eventi fino al termine dell'attività. Nella parte inferiore, l'attività divisa in blocchi consente al gestore di eventi di essere eseguito prima di quanto avrebbe altrimenti.
. Una visualizzazione del controllo del cedimento del controllo al thread principale. In alto, il rendimento avviene solo dopo che un'attività viene eseguita fino al completamento, il che significa che il completamento delle attività può richiedere più tempo prima di restituire il controllo al thread principale. In basso, il rendimento è fatto esplicitamente, suddividendo un'attività lunga in diverse attività più piccole. In questo modo le interazioni degli utenti possono essere eseguite più rapidamente, migliorando la reattività dell'input e l'INP.

Quando cedi in modo esplicito, dici al browser "Ehi, capisco che il lavoro che sto per fare potrebbe richiedere un po' di tempo e non voglio che tu debba svolgere tutto tutto questo prima di rispondere all'input dell'utente o ad altre attività che potrebbero essere importanti. Si tratta di un prezioso strumento per gli sviluppatori che può fare molto per migliorare l'esperienza utente.

Il problema delle attuali strategie di rendimento

Un metodo comune di rendimento utilizza setTimeout con un valore di timeout pari a 0. Questo funziona perché il callback passato a setTimeout sposterà il lavoro rimanente in un'attività separata che verrà messa in coda per l'esecuzione successiva. Piuttosto che aspettare che il browser ceda autonomamente, intendiamo dire "suddividere questa grossa fetta di lavoro in parti più piccole".

Tuttavia, cedere con setTimeout comporta un effetto collaterale potenzialmente indesiderato: il lavoro che segue dopo il punto di snervamento andrà in fondo alla coda di attività. Le attività pianificate dalle interazioni degli utenti continueranno a essere posizionate in cima alla coda, come dovrebbero, ma il lavoro rimanente che volevi svolgere dopo aver ceduto in modo esplicito potrebbe finire per essere ulteriormente ritardato da altre attività di origini concorrenti che erano in coda prima.

Per vedere la funzionalità in azione, prova questa demo di Glitch o sperimentala nella versione incorporata di seguito. La demo è costituita da alcuni pulsanti su cui puoi fare clic e da una casella sottostante che registra le attività in corso. Quando viene visualizzata la pagina, esegui le seguenti azioni:

  1. Fai clic sul pulsante in alto con l'etichetta Esegui attività periodicamente, che pianificherà l'esecuzione del blocco delle attività di tanto in tanto. Quando fai clic su questo pulsante, il log delle attività verrà completato con diversi messaggi contenenti la dicitura Esecuzione di blocco in corso con setInterval.
  2. Quindi, fai clic sul pulsante Esegui ciclo, ottenendo setTimeout a ogni iterazione.

Noterai che nella parte inferiore della demo troverai qualcosa di simile a questo:

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 la "fine della coda di attività" comportamento che si verifica quando si cede con setTimeout. Il ciclo in esecuzione elabora cinque elementi e restituisce setTimeout dopo che sono stati elaborati ciascuno.

Questo illustra un problema comune sul web: non è insolito che uno script, in particolare uno script di terze parti, registri una funzione timer che esegue il lavoro su un intervallo di tempo. "Fine della coda di attività" Il comportamento associato al rendimento con setTimeout significa che il lavoro proveniente da altre origini di attività potrebbe essere accodato prima del lavoro rimanente che il loop deve svolgere dopo il rendimento.

A seconda della tua applicazione, questo potrebbe essere o meno un risultato desiderabile, ma in molti casi, questo comportamento è il motivo per cui gli sviluppatori possono sentirsi riluttanti a rinunciare facilmente al controllo del thread principale. La restituzione è positiva perché le interazioni degli utenti hanno la possibilità di essere eseguite prima, ma consente anche ad altre attività di interazione non dell'utente di ottenere tempo anche sul thread principale. È un problema reale, ma scheduler.yield può aiutarlo a risolverlo!

Inserisci scheduler.yield

scheduler.yield è contrassegnato come funzionalità sperimentale della piattaforma web dalla versione 115 di Chrome. Una domanda che potresti avere è: "Perché ho bisogno di una funzione speciale per ottenere quando setTimeout la fa già?"

Vale la pena notare che il rendimento non era un obiettivo di progettazione di setTimeout, ma un effetto collaterale positivo nella pianificazione di un callback da eseguire in un momento successivo, anche con un valore di timeout pari a 0 specificato. Tuttavia, è più importante ricordare che, cedendo con setTimeout, il lavoro rimanente viene spostato indietro della coda di attività. Per impostazione predefinita, scheduler.yield invia il lavoro rimanente in prima della coda. Ciò significa che il lavoro che volevi 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 restituisce al thread principale e restituisce un Promise quando viene chiamata. 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:

  1. Vai a chrome://flags.
  2. Attiva l'esperimento Funzionalità sperimentali della piattaforma web. Al termine potresti dover 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 ciclo, ottenendo scheduler.yield a ogni iterazione.

L'output nella casella nella parte inferiore della pagina sarà simile al seguente:

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 prevede l'uso di setTimeout, puoi vedere che il ciclo, anche se genera dopo ogni iterazione, non invia il lavoro rimanente in fondo alla coda, ma in primo piano. Questo ti offre il meglio delle due modalità: puoi cedere per migliorare la reattività degli input sul tuo sito web, ma anche fare in modo che il lavoro che vuoi terminare dopo aver ceduto non subisca ritardi.

Prova anche tu!

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

  1. Se vuoi sperimentare scheduler.yield localmente, digita e inserisci chrome://flags nella barra degli indirizzi di Chrome e 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 reali di Chromium su un'origine accessibile pubblicamente, dovrai registrarti alla prova dell'origine di scheduler.yield. In questo modo puoi sperimentare in sicurezza le funzionalità proposte per un determinato periodo di tempo e offrire al team di Chrome informazioni preziose sul loro utilizzo sul campo. Per ulteriori informazioni sul funzionamento delle prove dell'origine, leggi questa guida.

Il modo in cui utilizzi scheduler.yield, pur supportando i browser che non lo implementano, dipende dai tuoi obiettivi. Puoi utilizzare il polyfill ufficiale. Il polyfill è utile se si applica quanto segue alla tua situazione:

  1. Utilizzi già scheduler.postTask nella tua applicazione per pianificare le attività.
  2. Vuoi poter impostare le attività e produrre le priorità.
  3. Vuoi avere la possibilità di annullare o ridefinire le priorità delle attività tramite la classe TaskController offerta dall'API scheduler.postTask.

Se questo non descrive la tua situazione, il polyfill potrebbe non essere adatto a te. In questo caso, puoi eseguire il rollback autonomamente in un paio di modi. Il primo approccio utilizza scheduler.yield se disponibile, ma torna a setTimeout se non è disponibile:

// 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 intuire, i browser che non supportano scheduler.yield daranno risultati senza "prima coda" comportamento degli utenti. Se ciò significa che preferisci non cedere del tutto, puoi provare un altro approccio che utilizza scheduler.yield se è disponibile, ma che non genera alcun rendimento in caso contrario:

// 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'entusiasmante aggiunta all'API scheduler, che dovrebbe semplificare per gli sviluppatori il miglioramento della reattività rispetto alle strategie di rendimento attuali. Se l'API scheduler.yield ti sembra utile, partecipa alla nostra ricerca per aiutarci a migliorarla e inviaci un feedback su come migliorarla ulteriormente.

Immagine hero da Unsplash, di Jonathan Allison.