Migliore pianificazione JS con isInputPending()

Una nuova API JavaScript che può aiutarti a evitare l'equilibrio tra prestazioni di carico e reattività dell'input.

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

Caricare i video velocemente è difficile. Attualmente i siti che utilizzano JS per eseguire il rendering dei propri contenuti dover trovare un compromesso tra le prestazioni di carico e l'input reattività: eseguire tutto il lavoro necessario per la tutti contemporaneamente (prestazioni di caricamento migliori, minore reattività dell'input) oppure suddividere il lavoro in attività più piccole per garantire la reattività input e colorazione (prestazioni peggiori di caricamento, input migliori reattività).

Per eliminare la necessità di scendere a compromessi, Facebook ha proposto e implementato l'API isInputPending() in Chromium per migliorare la reattività senza cedere. In base al feedback sulla prova dell'origine, abbiamo apportato una serie di aggiornamenti e siamo lieti di annunciare che ora l'API viene spedita per impostazione predefinita in Chromium 87!

Compatibilità del browser

Supporto dei browser

  • Chrome: 87.
  • Edge: 87.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

isInputPending() è disponibile nei browser basati su Chromium a partire dalla versione 87. Nessun altro browser ha segnalato l'intenzione di distribuire l'API.

Sfondo

La maggior parte del lavoro nell'ecosistema JS di oggi viene svolta in un unico thread: il thread principale. Questo fornisce agli sviluppatori un solido modello di esecuzione, ma l'esperienza utente (in particolare la reattività) può risentirne drasticamente se lo script viene eseguito nel tempo. Se la pagina sta svolgendo molto lavoro mentre viene attivato un evento di input, Ad esempio, la pagina non gestirà l'evento di input del clic fino a quando vengono completate.

La best practice attuale consiste nell'affrontare questo problema interrompendo il di JavaScript in blocchi più piccoli. Durante il caricamento della pagina, è possibile eseguire un un po' di JavaScript per poi cedere e ritrasmettere il controllo al browser. La browser può quindi controllare la coda degli eventi di input e vedere se c'è qualcosa deve indicare alla pagina. Poi il browser può tornare a eseguire Blocchi JavaScript man mano che vengono aggiunti. Questa operazione aiuta, ma può causare altri problemi.

Ogni volta che la pagina restituisce il controllo al browser, occorre un po' di tempo per il browser per controllare la coda degli eventi di input, elaborare gli eventi e scegliere di blocco JavaScript. Mentre il browser risponde agli eventi più rapidamente, nel complesso il tempo di caricamento della pagina rallenta. E se diamo troppo spesso, la pagina si carica troppo lentamente. Se la resa avviene meno spesso, occorre più tempo prima che il browser rispondono agli eventi degli utenti e le persone si sentono frustrate. Non è divertente.

Un diagramma che mostra che quando esegui attività JS lunghe, il browser ha meno tempo per inviare eventi.

Noi di Facebook volevamo vedere come sarebbero le cose se trovassimo un un nuovo approccio al caricamento che eliminerebbe questo frustrante compromesso. Me abbiamo contattato i nostri amici di Chrome e abbiamo proposto per isInputPending(). L'API isInputPending() è la prima a utilizzare il concetto di interrompe gli input dell'utente sul web e consente il riconoscimento in grado di controllare l'input senza cedere al browser.

Un diagramma che mostra che isInputPending() consente al tuo JS di verificare se ci sono input utente in sospeso senza restituire completamente l'esecuzione al browser.

Dato l'interesse dimostrato per l'API, abbiamo collaborato con i nostri colleghi di Chrome per implementare e distribuire la funzionalità in Chromium. Con l'aiuto di Chrome ingegneri, abbiamo riscontrato una prova dell'origine (che consente a Chrome di testare le modifiche e ricevere feedback dagli sviluppatori prima del rilascio completo di un'API).

Abbiamo preso feedback dalla prova dell'origine e dagli altri membri del W3C Web Performance Working Group e ha implementato modifiche all'API.

Esempio: scheduler più redditizio

Supponiamo di dover svolgere del lavoro per bloccare il display pagina, ad esempio generazione del markup dai componenti, esclusione dei numeri primi disegnando semplicemente una rotellina di caricamento. Ognuna di queste è suddivisa in una dell'elemento di lavoro. Usando il pattern dello scheduler, vediamo come potremmo elaborare il nostro lavoro in un'ipotetica funzione processWorkQueue():

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Richiamando processWorkQueue() in un secondo momento in una nuova macro attività tramite setTimeout(), il browser ha la possibilità di rispondere in qualche modo all'input (può i gestori di eventi prima della ripresa del lavoro) rimanendo in grado di eseguire relativamente senza interruzioni. Però potremmo essere riprogrammati a lungo da altri lavori che vuole controllare il loop di eventi, oppure ottenere fino a QUANTUM millisecondi in più di latenza degli eventi.

Questo va bene, ma possiamo fare di meglio? Assolutamente sì!

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

Introducendo una chiamata a navigator.scheduling.isInputPending(), possiamo rispondere agli input più velocemente garantendo al contempo che il nostro sistema di blocco viene eseguita senza interruzioni. Se non ci interessa gestire a parte l'input (ad es. dipingere) fino a quando il lavoro non è completo, possiamo aumentare anche la lunghezza di QUANTUM.

Per impostazione predefinita, "continua" eventi non vengono restituiti da isInputPending(). Questi includono mousemove, pointermove e altri. Se ti interessa cedere anche queste, nessun problema. Fornendo un oggetto a isInputPending() con includeContinuous impostato su true, è tutto pronto:

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

È tutto. I framework come React stanno integrando il supporto di isInputPending() nei delle librerie di pianificazione principali utilizzando una logica simile. Speriamo che questo porterà sviluppatori che utilizzano questi framework per poter trarre vantaggio da isInputPending() dietro le quinte senza modifiche significative.

Fornire non è sempre un cattivo segno

Vale la pena notare che produrre meno non è la soluzione giusta per ogni utilizzo per verificare se è così. Esistono diversi motivi per restituire il controllo al browser oltre che per elaborare eventi di input, ad esempio eseguire il rendering ed eseguire altri script della pagina.

Esistono casi in cui il browser non è in grado di attribuire correttamente lo stato "In sospeso" di input. In particolare, l'impostazione di clip e maschere complesse per multiorigine Gli iframe potrebbero segnalare falsi negativi (ad es. isInputPending() potrebbe restituire inaspettatamente false quando si sceglie come target questi frame). Assicurati di cedere abbastanza spesso se il tuo sito richiede interazioni con frame secondari stilizzati.

Presta attenzione anche alle altre pagine che condividono un loop di eventi. Su piattaforme come come Chrome per Android, è piuttosto comune che più origini condividano ciclo. isInputPending() non restituirà mai true se l'input viene inviato a un multiorigine, pertanto le pagine in background potrebbero interferire con il reattività delle pagine in primo piano. Potresti voler ridurre, posticipare o cedere quando si lavora in background utilizzando l'API Page Visibility.

Ti invitiamo a utilizzare isInputPending() con discrezione. Se non sono bloccare gli utenti e interagire con gli altri nel loop degli eventi cedendo con maggiore frequenza. Le attività lunghe possono essere dannose.

Feedback

Conclusione

Siamo entusiasti del lancio di isInputPending() e che gli sviluppatori possano per iniziare a utilizzarlo oggi stesso. Questa API è la prima volta che Facebook crea un una nuova API web e l'abbiamo passata dall'incubazione delle idee alla proposta di standard la spedizione in un browser. Vorremmo ringraziare tutti coloro che ci hanno aiutato e ringraziare in modo speciale tutti i membri di Chrome che ci hanno aiutato a dare il meglio di noi questa idea e fartela spedire!

Foto hero di Will H McMahan su Rimuovi schermo.