Maak kennis met de proefversie van planner.yield origin

Het bouwen van websites die snel reageren op gebruikersinvoer is een van de meest uitdagende aspecten van webprestaties geweest, een aspect waaraan het Chrome-team hard heeft gewerkt om webontwikkelaars te helpen hieraan te voldoen. Nog dit jaar werd aangekondigd dat de Interaction to Next Paint (INP)-statistiek zou overgaan van experimenteel naar in behandeling zijnde status. Het is nu klaar om First Input Delay (FID) in maart 2024 te vervangen als Core Web Vital.

In een voortdurende inspanning om nieuwe API's te leveren waarmee webontwikkelaars hun websites zo snel mogelijk kunnen maken, voert het Chrome-team momenteel een origin-proefversie uit voor scheduler.yield , te beginnen in versie 115 van Chrome. scheduler.yield is een voorgestelde nieuwe toevoeging aan de planner-API die zowel een eenvoudigere als een betere manier mogelijk maakt om de controle terug te geven aan de hoofdthread dan de methoden waarop traditioneel werd vertrouwd .

Op het opleveren

JavaScript gebruikt het run-to-completion-model om met taken om te gaan. Dit betekent dat, wanneer een taak op de hoofdthread wordt uitgevoerd, die taak zo lang doorloopt als nodig is om te voltooien. Wanneer een taak is voltooid, wordt de controle teruggegeven aan de hoofdthread, waardoor de hoofdthread de volgende taak in de wachtrij kan verwerken.

Afgezien van extreme gevallen waarin een taak nooit wordt voltooid, zoals bijvoorbeeld bij een oneindige lus, is yielding een onvermijdelijk aspect van de taakplanningslogica van JavaScript. Het zal gebeuren, het is slechts een kwestie van wanneer , en eerder is beter dan laat. Wanneer het te lang duurt om taken uit te voeren (meer dan 50 milliseconden om precies te zijn), worden ze beschouwd als lange taken .

Lange taken zijn een bron van slechte responsiviteit van pagina's, omdat ze het vermogen van de browser om te reageren op gebruikersinvoer vertragen. Hoe vaker lange taken voorkomen (en hoe langer ze duren), hoe waarschijnlijker het is dat gebruikers de indruk krijgen dat de pagina traag is, of zelfs het gevoel hebben dat deze helemaal niet werkt.

Het feit dat uw code een taak in de browser start, betekent echter niet dat u moet wachten tot die taak is voltooid voordat u de controle weer overgeeft aan de hoofdthread. U kunt de reactiesnelheid op gebruikersinvoer op een pagina verbeteren door expliciet op te geven in een taak, waardoor de taak wordt opgesplitst en bij de volgende beschikbare gelegenheid moet worden voltooid. Hierdoor kunnen andere taken eerder tijd krijgen voor de hoofdthread dan wanneer ze moesten wachten tot lange taken waren voltooid.

Een afbeelding van hoe het opsplitsen van een taak een betere invoerrespons kan bevorderen. Bovenaan blokkeert een lange taak het uitvoeren van een gebeurtenishandler totdat de taak is voltooid. Onderaan zorgt de opgesplitste taak ervoor dat de gebeurtenishandler sneller kan worden uitgevoerd dan anders het geval zou zijn geweest.
Een visualisatie van het teruggeven van de controle aan de rode draad. Bovenal vindt het opleveren pas plaats nadat een taak is voltooid, wat betekent dat het langer kan duren voordat taken zijn voltooid voordat de controle weer bij de rode draad komt. In wezen wordt er expliciet toegegeven, waarbij een lange taak in meerdere kleinere taken wordt opgedeeld. Hierdoor kunnen gebruikersinteracties sneller plaatsvinden, wat de invoerrespons en INP verbetert.

Wanneer je expliciet toegeeft, zeg je tegen de browser: "Hé, ik begrijp dat het werk dat ik ga doen een tijdje kan duren, en ik wil niet dat je al dat werk moet doen voordat je reageert op gebruikersinvoer of andere taken die ook belangrijk kunnen zijn". Het is een waardevol hulpmiddel in de gereedschapskist van een ontwikkelaar dat een grote bijdrage kan leveren aan het verbeteren van de gebruikerservaring.

Het probleem met de huidige rendementsstrategieën

Een veelgebruikte methode voor het opleveren maakt gebruik van setTimeout met een time-outwaarde van 0 . Dit werkt omdat de callback die wordt doorgegeven aan setTimeout het resterende werk naar een afzonderlijke taak verplaatst die in de wachtrij wordt geplaatst voor daaropvolgende uitvoering. In plaats van te wachten tot de browser vanzelf opgeeft, zeg je: "laten we dit grote deel van het werk in kleinere stukjes opdelen".

Het opleveren met setTimeout heeft echter een potentieel ongewenst neveneffect: het werk dat na het opbrengstpunt komt, komt achteraan in de takenwachtrij terecht. Taken die door gebruikersinteracties zijn gepland, komen nog steeds vooraan in de wachtrij, zoals het hoort, maar het resterende werk dat je wilde doen nadat je er expliciet voor had opgegeven, zou uiteindelijk verder kunnen worden vertraagd door andere taken van concurrerende bronnen die daarvoor in de wachtrij stonden.

Om dit in actie te zien, probeer deze Glitch-demo - of experimenteer ermee in de ingesloten versie hieronder. De demo bestaat uit een paar knoppen waarop u kunt klikken, en een vak eronder dat registreert wanneer taken worden uitgevoerd. Wanneer u op de pagina terechtkomt, voert u de volgende acties uit:

  1. Klik op de bovenste knop met het label Taken periodiek uitvoeren , waarmee wordt gepland dat blokkeertaken zo nu en dan worden uitgevoerd. Wanneer u op deze knop klikt, wordt het takenlogboek gevuld met verschillende berichten met de tekst Ran blocking task with setInterval .
  2. Klik vervolgens op de knop met het label Run loop, waarbij bij elke iteratie setTimeout wordt weergegeven .

U zult merken dat het vak onderaan de demo er ongeveer zo uitziet:

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

Deze uitvoer demonstreert het gedrag 'einde van taakwachtrij' dat optreedt bij het opleveren met setTimeout . De lus die wordt uitgevoerd, verwerkt vijf items en levert setTimeout op nadat ze allemaal zijn verwerkt.

Dit illustreert een veelvoorkomend probleem op internet: het is niet ongebruikelijk dat een script (vooral een script van derden) een timerfunctie registreert die met een bepaald interval werk uitvoert. Het 'einde van de takenwachtrij'-gedrag dat gepaard gaat met het opleveren met setTimeout betekent dat werk van andere taakbronnen in de wachtrij kan worden geplaatst vóór het resterende werk dat de lus moet doen na het opleveren.

Afhankelijk van uw toepassing kan dit wel of niet een wenselijk resultaat zijn, maar in veel gevallen is dit gedrag de reden waarom ontwikkelaars zich terughoudend kunnen voelen om de controle over de rode draad zo gemakkelijk uit handen te geven. Opbrengst is goed omdat gebruikersinteracties de mogelijkheid hebben om eerder te starten, maar het zorgt er ook voor dat ander interactiewerk dat niet met gebruikers te maken heeft, ook tijd aan de hoofdlijn kan besteden. Het is een echt probleem, maar scheduler.yield kan je helpen het op te lossen!

Voer scheduler.yield in

scheduler.yield is sinds versie 115 van Chrome beschikbaar achter een vlag als een experimentele webplatformfunctie . Een vraag die je misschien hebt is: "Waarom heb ik een speciale functie nodig om op te leveren als setTimeout dit al doet?"

Het is de moeite waard om op te merken dat opbrengst niet een ontwerpdoel was van setTimeout , maar eerder een leuk neveneffect bij het plannen van een callback om op een later tijdstip in de toekomst te worden uitgevoerd, zelfs als er een time-outwaarde van 0 was opgegeven. Wat echter belangrijker is om te onthouden, is dat het opgeven met setTimeout het resterende werk naar de achterkant van de takenwachtrij stuurt. scheduler.yield stuurt standaard het resterende werk naar de eerste plaats in de wachtrij. Dit betekent dat werk dat u onmiddellijk na het opleveren wilde hervatten, niet op de achtergrond komt ten opzichte van taken uit andere bronnen (met de opmerkelijke uitzondering van gebruikersinteracties).

scheduler.yield is een functie die toegeeft aan de hoofdthread en een Promise retourneert wanneer deze wordt aangeroepen. Dit betekent dat u erop kunt await in een async functie:

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

  // Yield!
  await scheduler.yield();

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

Ga als volgt te werk om scheduler.yield in actie te zien:

  1. Navigeer naar chrome://flags .
  2. Schakel het experimentele webplatformfunctiesexperiment in. Mogelijk moet u Chrome opnieuw opstarten nadat u dit heeft gedaan.
  3. Navigeer naar de demopagina of gebruik de ingesloten versie ervan onder deze lijst.
  4. Klik op de bovenste knop met het label Taken periodiek uitvoeren .
  5. Klik ten slotte op de knop met het label Run loop, waarbij bij elke iteratie scheduler.yield wordt weergegeven .

De uitvoer in het vak onderaan de pagina ziet er ongeveer zo uit:

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

In tegenstelling tot de demo die oplevert met setTimeout , kun je zien dat de lus (ook al wordt deze na elke iteratie opgeleverd) het resterende werk niet naar achteren in de wachtrij stuurt, maar eerder naar de voorkant ervan. Dit geeft u het beste van twee werelden: u kunt de responsiviteit van de invoer op uw website verbeteren, maar u kunt er ook voor zorgen dat het werk dat u na de oplevering wilde afmaken, geen vertraging oploopt.

Probeer het eens!

Als scheduler.yield je interessant lijkt en je het wilt uitproberen, kun je dat op twee manieren doen vanaf versie 115 van Chrome:

  1. Als u lokaal met scheduler.yield wilt experimenteren, typt u chrome://flags in de adresbalk van Chrome en selecteert u Inschakelen in de vervolgkeuzelijst in het gedeelte Experimentele webplatformfuncties . Hierdoor wordt scheduler.yield (en eventuele andere experimentele functies) alleen beschikbaar in uw exemplaar van Chrome.
  2. Als je scheduler.yield wilt inschakelen voor echte Chromium-gebruikers op een openbaar toegankelijke oorsprong, moet je je aanmelden voor de proefversie scheduler.yield origin . Hierdoor kunt u gedurende een bepaalde periode veilig experimenteren met voorgestelde functies en krijgt het Chrome-team waardevolle inzichten in hoe deze functies in het veld worden gebruikt. Lees deze gids voor meer informatie over hoe herkomstproeven werken.

Hoe u scheduler.yield gebruikt, terwijl u nog steeds browsers ondersteunt die dit niet implementeren, hangt af van wat uw doelen zijn. U kunt de officiële polyfill gebruiken. De polyfill is handig als het volgende op uw situatie van toepassing is:

  1. U gebruikt scheduler.postTask al in uw toepassing om taken te plannen.
  2. Je wilt taken kunnen stellen en prioriteiten kunnen stellen.
  3. U wilt taken kunnen annuleren of opnieuw prioriteren via de TaskController klasse die de scheduler.postTask API biedt.

Als dit uw situatie niet beschrijft, is de polyfill wellicht niet geschikt voor u. In dat geval kunt u op een aantal manieren uw eigen fallback uitvoeren. De eerste benadering maakt gebruik van scheduler.yield als dit beschikbaar is, maar valt terug op setTimeout als dit niet het geval is:

// 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:
  // ...
}

Dit kan werken, maar zoals je misschien wel vermoedt, zullen browsers die scheduler.yield niet ondersteunen, wel presteren zonder "vooraan in de wachtrij"-gedrag. Als dat betekent dat u liever helemaal geen opbrengst wilt, kunt u een andere aanpak proberen die scheduler.yield gebruikt als deze beschikbaar is, maar helemaal geen opbrengst oplevert als dat niet het geval is:

// 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 is een opwindende toevoeging aan de planner-API, een die het hopelijk voor ontwikkelaars gemakkelijker zal maken om de responsiviteit te verbeteren dan de huidige opbrengststrategieën. Als scheduler.yield u een nuttige API lijkt, neem dan deel aan ons onderzoek om deze te helpen verbeteren en geef feedback over hoe deze verder kan worden verbeterd.

Hero-afbeelding van Unsplash , door Jonathan Allison .