Betere JS-planning met isInputPending()

Een nieuwe JavaScript-API die u kan helpen de wisselwerking tussen laadprestaties en invoerresponsiviteit te vermijden.

Nate Schloss
Nate Schloss
Andreas Comminos
Andrew Comminos

Snel laden is moeilijk. Sites die JS gebruiken om hun inhoud weer te geven, moeten momenteel een afweging maken tussen laadprestaties en invoerresponsiviteit: ofwel al het werk dat nodig is voor weergave in één keer uitvoeren (betere laadprestaties, slechtere invoerresponsiviteit), of het werk in kleinere stukken verdelen taken uitvoeren om responsief te blijven op invoer en verf (slechtere laadprestaties, betere reactiesnelheid op invoer).

Om de noodzaak om deze afweging te maken te elimineren, heeft Facebook de isInputPending() API in Chromium voorgesteld en geïmplementeerd om de responsiviteit te verbeteren zonder concessies te doen. Op basis van de feedback van de origin-proefversie hebben we een aantal updates aan de API aangebracht en kunnen we met trots aankondigen dat de API nu standaard wordt geleverd in Chromium 87!

Browsercompatibiliteit

Browserondersteuning

  • Chroom: 87.
  • Rand: 87.
  • Firefox: niet ondersteund.
  • Safari: niet ondersteund.

Bron

isInputPending() wordt geleverd in Chromium-gebaseerde browsers vanaf versie 87. Geen enkele andere browser heeft aangegeven de API te willen verzenden.

Achtergrond

Het meeste werk in het huidige JS-ecosysteem wordt gedaan op één enkele thread: de hoofdthread. Dit biedt ontwikkelaars een robuust uitvoeringsmodel, maar de gebruikerservaring (met name de responsiviteit) kan drastisch lijden als het script lange tijd wordt uitgevoerd. Als de pagina bijvoorbeeld veel werk doet terwijl een invoergebeurtenis wordt geactiveerd, zal de pagina de klikinvoergebeurtenis pas verwerken nadat dat werk is voltooid.

De huidige beste praktijk is om dit probleem aan te pakken door JavaScript in kleinere blokken op te delen. Terwijl de pagina wordt geladen, kan de pagina een stukje JavaScript uitvoeren en vervolgens de controle doorgeven aan de browser. De browser kan vervolgens de wachtrij voor invoergebeurtenissen controleren en kijken of er iets is dat de pagina moet vertellen. Vervolgens kan de browser doorgaan met het uitvoeren van de JavaScript-blokken zodra deze worden toegevoegd. Dit helpt, maar het kan andere problemen veroorzaken.

Elke keer dat de pagina de controle teruggeeft aan de browser, duurt het enige tijd voordat de browser de wachtrij voor invoergebeurtenissen controleert, gebeurtenissen verwerkt en het volgende JavaScript-blok oppikt. Hoewel de browser sneller op gebeurtenissen reageert, wordt de algehele laadtijd van de pagina vertraagd. En als we te vaak toegeven, laadt de pagina te langzaam. Als we minder vaak toegeven, duurt het langer voordat de browser reageert op gebruikersgebeurtenissen en raken mensen gefrustreerd. Niet leuk.

Een diagram dat laat zien dat wanneer u lange JS-taken uitvoert, de browser minder tijd heeft om gebeurtenissen te verzenden.

Bij Facebook wilden we zien hoe de dingen eruit zouden zien als we een nieuwe aanpak voor het laden zouden bedenken die deze frustrerende afweging zou elimineren. We hebben hierover contact opgenomen met onze vrienden bij Chrome en kwamen met het voorstel voor isInputPending() . De isInputPending() API is de eerste die het concept van interrupts gebruikt voor gebruikersinvoer op internet, en zorgt ervoor dat JavaScript kan controleren op invoer zonder toe te geven aan de browser.

Een diagram dat laat zien dat isInputPending() uw JS in staat stelt te controleren of er gebruikersinvoer in behandeling is, zonder de uitvoering volledig terug te geven aan de browser.

Omdat er interesse was in de API, hebben we samengewerkt met onze collega's bij Chrome om de functie in Chromium te implementeren en te leveren. Met hulp van de Chrome-technici hebben we de patches achter een origin-proefversie kunnen krijgen (wat een manier is voor Chrome om wijzigingen te testen en feedback van ontwikkelaars te krijgen voordat een API volledig wordt vrijgegeven).

We hebben nu feedback gekregen van de origin-proef en van de andere leden van de W3C Web Performance Working Group en wijzigingen in de API geïmplementeerd.

Voorbeeld: een meer renderende planner

Stel dat u een heleboel beeldschermblokkerende werkzaamheden moet verrichten om uw pagina te laden, bijvoorbeeld het genereren van markeringen uit componenten, het wegwerken van priemgetallen, of gewoon het tekenen van een coole laadspinner. Elk hiervan is onderverdeeld in een afzonderlijk werkitem. Laten we met behulp van het plannerpatroon schetsen hoe we ons werk zouden kunnen verwerken in een hypothetische processWorkQueue() -functie:

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();
}

Door processWorkQueue() later in een nieuwe macrotaak aan te roepen via setTimeout() , geven we de browser de mogelijkheid om enigszins te reageren op invoer (hij kan gebeurtenishandlers uitvoeren voordat het werk wordt hervat), terwijl hij er toch in slaagt relatief ononderbroken te werken. Het kan echter zijn dat we lange tijd uitgesteld worden door ander werk dat controle wil hebben over de gebeurtenislus, of een extra QUANTUM milliseconden aan gebeurtenislatentie kan bereiken.

Dit is oké, maar kunnen we het beter doen? Absoluut!

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();
}

Door een aanroep naar navigator.scheduling.isInputPending() te introduceren, kunnen we sneller op invoer reageren en er toch voor zorgen dat ons weergaveblokkeringswerk anders ononderbroken wordt uitgevoerd. Als we niets anders willen doen dan invoer (bijvoorbeeld schilderen) totdat het werk voltooid is, kunnen we ook handig de lengte van QUANTUM vergroten.

Standaard worden 'continue' gebeurtenissen niet geretourneerd door isInputPending() . Deze omvatten mousemove , pointermove en andere. Als u hier ook interesse in heeft, is dat geen probleem. Door een object aan te bieden aan isInputPending() met includeContinuous ingesteld op true , zijn we klaar om te gaan:

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();
}

Dat is het! Frameworks zoals React bouwen isInputPending() ondersteuning in hun kernplanningsbibliotheken met behulp van vergelijkbare logica. Hopelijk zal dit ervoor zorgen dat ontwikkelaars die deze raamwerken gebruiken achter de schermen kunnen profiteren van isInputPending() zonder noemenswaardige herschrijvingen.

Opbrengst is niet altijd slecht

Het is de moeite waard om op te merken dat minder opbrengst niet voor elke gebruikssituatie de juiste oplossing is. Er zijn veel redenen om de controle terug te geven aan de browser, behalve voor het verwerken van invoergebeurtenissen, zoals het uitvoeren van rendering en het uitvoeren van andere scripts op de pagina.

Er zijn gevallen waarin de browser niet in staat is om lopende invoergebeurtenissen correct toe te schrijven. Met name het instellen van complexe clips en maskers voor cross-origin iframes kan vals-negatieven opleveren (dat wil zeggen dat isInputPending() onverwacht false retourneert wanneer deze frames worden getarget). Zorg ervoor dat u vaak genoeg resultaat oplevert als uw site interacties met gestileerde subframes vereist.

Houd ook rekening met andere pagina's die een gebeurtenislus delen. Op platforms zoals Chrome voor Android is het vrij gebruikelijk dat meerdere oorsprongen een gebeurtenislus delen. isInputPending() retourneert nooit true als invoer wordt verzonden naar een cross-origin frame, en dus kunnen achtergrondpagina's de responsiviteit van voorgrondpagina's verstoren. Mogelijk wilt u de werkzaamheden vaker beperken, uitstellen of opbrengen wanneer u op de achtergrond werkt met de Page Visibility API .

We raden u aan om isInputPending() met discretie te gebruiken. Als er geen gebruikersblokkeringswerk moet worden gedaan, wees dan aardig voor anderen in de gebeurtenislus door vaker toe te geven. Lange taken kunnen schadelijk zijn .

Feedback

  • Laat feedback achter over de specificatie in de repository is-input-pending .
  • Neem contact op met @acomminos (een van de spec-auteurs) op Twitter.

Conclusie

We zijn blij dat isInputPending() wordt gelanceerd en dat ontwikkelaars het vandaag kunnen gaan gebruiken. Deze API is de eerste keer dat Facebook een nieuwe web-API heeft gebouwd en deze van het uitbroeden van ideeën tot standaardvoorstellen heeft omgezet in daadwerkelijke verzending in een browser. We willen graag iedereen bedanken die ons heeft geholpen dit punt te bereiken, en een speciale shoutout geven aan iedereen bij Chrome die ons heeft geholpen dit idee uit te werken en te versturen!

Heldenfoto door Will H McMahan op Unsplash .