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.
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:
- 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
. - 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:
- Vai a
chrome://flags
. - Attiva l'esperimento Funzionalità sperimentali della piattaforma web. Al termine potresti dover riavviare Chrome.
- Vai alla pagina demo o utilizza la versione incorporata sotto questo elenco.
- Fai clic sul pulsante in alto con l'etichetta Esegui attività periodicamente.
- 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:
- Se vuoi sperimentare
scheduler.yield
localmente, digita e inseriscichrome://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. - Se vuoi abilitare
scheduler.yield
per utenti reali di Chromium su un'origine accessibile pubblicamente, dovrai registrarti alla prova dell'origine discheduler.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:
- Utilizzi già
scheduler.postTask
nella tua applicazione per pianificare le attività. - Vuoi poter impostare le attività e produrre le priorità.
- Vuoi avere la possibilità di annullare o ridefinire le priorità delle attività tramite la classe
TaskController
offerta dall'APIscheduler.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.