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 – een aspect waar het Chrome-team hard aan heeft gewerkt om webontwikkelaars te helpen. Dit jaar werd aangekondigd dat de Interaction to Next Paint (INP)-metriek van experimenteel naar in behandeling zou gaan. Deze metriek staat nu klaar om First Input Delay (FID) te vervangen als Core Web Vital in maart 2024.

In een voortdurende poging om nieuwe API's te leveren waarmee webontwikkelaars hun websites zo snel mogelijk kunnen maken, voert het Chrome-team momenteel een proefversie uit voor scheduler.yield Deze proefversie is beschikbaar vanaf versie 115 van Chrome. scheduler.yield is een voorgestelde nieuwe toevoeging aan de scheduler-API. Hiermee is het een eenvoudigere en betere manier om de controle terug te geven aan de hoofdthread dan met de methoden waarop traditioneel werd vertrouwd .

Over toegeven

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

Afgezien van extreme gevallen waarin een taak nooit eindigt – zoals bijvoorbeeld een oneindige lus – is 'yielding' een onvermijdelijk aspect van de taakplanningslogica van JavaScript. Het zal gebeuren, het is alleen een kwestie van wanneer , en liever eerder dan later. Wanneer taken te lang duren – meer dan 50 milliseconden, om precies te zijn – worden ze beschouwd als lange taken .

Lange taken zijn een bron van slechte paginaresponsiviteit, omdat ze de browser vertragen om te reageren op gebruikersinvoer. Hoe vaker lange taken voorkomen – en hoe langer ze duren – hoe groter de kans dat gebruikers de indruk krijgen dat de pagina traag is, of zelfs dat deze helemaal niet werkt.

Maar alleen omdat je code een taak in de browser start, betekent dit niet dat je moet wachten tot die taak is voltooid voordat de controle weer aan de hoofdthread wordt overgedragen. Je kunt de responsiviteit op gebruikersinvoer op een pagina verbeteren door expliciet 'future' in een taak op te nemen, waardoor de taak wordt opgedeeld en bij de eerstvolgende gelegenheid wordt voltooid. Hierdoor kunnen andere taken sneller tijd op de hoofdthread krijgen dan wanneer ze zouden moeten wachten tot lange taken zijn voltooid.

Een weergave van hoe het opsplitsen van een taak de invoerresponsiviteit kan verbeteren. Bovenaan blokkeert een lange taak de uitvoering van een event handler totdat de taak is voltooid. Onderaan zorgt de opgesplitste taak ervoor dat de event handler sneller kan worden uitgevoerd dan anders het geval zou zijn geweest.
Een visualisatie van het teruggeven van de controle aan de hoofdthread. Bovenaan vindt het teruggeven pas plaats nadat een taak is voltooid, wat betekent dat taken langer kunnen duren voordat de controle teruggegeven wordt aan de hoofdthread. Onderaan wordt het teruggeven expliciet gedaan, waarbij een lange taak wordt opgedeeld in meerdere kleinere taken. Dit zorgt ervoor dat gebruikersinteracties sneller kunnen worden uitgevoerd, wat de invoerresponsiviteit en INP verbetert.

Wanneer je expliciet toegeeft, zeg je tegen de browser: "Hé, ik begrijp dat het werk dat ik ga doen even kan duren, en ik wil niet dat jij al dat werk moet doen voordat je op gebruikersinvoer reageert of andere taken uitvoert die ook belangrijk kunnen zijn." Het is een waardevolle tool in de gereedschapskist van een ontwikkelaar die de gebruikerservaring aanzienlijk kan verbeteren.

Het probleem met de huidige rendementstrategieën

Een veelgebruikte methode voor het opgeven van taken is setTimeout met een time-outwaarde van 0 Dit werkt omdat de callback die aan setTimeout wordt doorgegeven het resterende werk naar een aparte taak verplaatst die in de wachtrij wordt geplaatst voor verdere uitvoering. In plaats van te wachten tot de browser zelf opgeeft, zeg je: "Verdeel dit grote stuk werk in kleinere stukken".

Het opgeven met setTimeout heeft echter een mogelijk ongewenst neveneffect: het werk dat na het opgeven komt, komt achteraan in de wachtrij te staan. Taken die gepland zijn door gebruikersinteracties komen nog steeds vooraan in de wachtrij te staan, zoals het hoort. Het resterende werk dat u na expliciet opgeven wilde doen, kan echter verder worden vertraagd door andere taken van concurrerende bronnen die ervoor in de wachtrij stonden.

Om dit in actie te zien, probeer deze Codepen-demo — of experimenteer ermee in de volgende ingebedde versie. De demo bestaat uit een paar knoppen waarop je kunt klikken, met daaronder een vak dat registreert wanneer taken worden uitgevoerd. Voer de volgende acties uit wanneer je op de pagina terechtkomt:

  1. Klik op de bovenste knop Taken periodiek uitvoeren om blokkerende taken zo nu en dan uit te voeren. Wanneer u op deze knop klikt, worden in het taaklogboek verschillende berichten weergegeven, zoals 'Blokkerende taak uitgevoerd met setInterval .
  2. Klik vervolgens op de knop Run loop, waarbij bij elke iteratie setTimeout wordt gegenereerd .

U zult merken dat het vakje 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 "einde van de taakwachtrij"-gedrag dat optreedt bij het opgeven met setTimeout . De lus die wordt uitgevoerd, verwerkt vijf items en geeft een resultaat met setTimeout nadat elk item is verwerkt.

Dit illustreert een veelvoorkomend probleem op het web: het is niet ongebruikelijk dat een script, met name een script van een derde partij, een timerfunctie registreert die werk op een bepaald interval uitvoert. Het "einde van de taakwachtrij"-gedrag dat optreedt bij het opgeven met setTimeout betekent dat werk van andere taakbronnen mogelijk in de wachtrij wordt geplaatst vóór het resterende werk dat de lus na het opgeven moet doen.

Afhankelijk van uw toepassing kan dit een wenselijke uitkomst zijn, maar in veel gevallen is dit gedrag de reden waarom ontwikkelaars aarzelen om de controle over de hoofdthread zo snel op te geven. Yielding is goed omdat gebruikersinteracties sneller kunnen worden uitgevoerd, maar het zorgt er ook voor dat ander werk dat niet door gebruikers wordt uitgevoerd, ook tijd krijgt op de hoofdthread. Het is een reëel probleem, maar scheduler.yield kan helpen het op te lossen!

Voer scheduler.yield in

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

Het is belangrijk om op te merken dat yielding geen 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 met een opgegeven time-outwaarde van 0 Belangrijker om te onthouden is echter dat yielding met setTimeout het resterende werk naar achteren in de taakwachtrij stuurt. Standaard stuurt scheduler.yield het resterende werk naar voren in de wachtrij. Dit betekent dat werk dat u direct na yielding wilde hervatten, niet ondergeschikt wordt gemaakt aan taken van andere bronnen (met de opmerkelijke uitzondering van gebruikersinteracties).

scheduler.yield is een functie die aan de hoofdthread levert en een Promise retourneert wanneer deze wordt aangeroepen. Dit betekent dat je hierop kunt await in een async -functie:

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

  // Yield!
  await scheduler.yield();

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

Om scheduler.yield in actie te zien, doet u het volgende:

  1. Ga naar chrome://flags .
  2. Schakel het experiment 'Experimentele webplatformfuncties' in. Mogelijk moet u Chrome hierna opnieuw opstarten.
  3. Navigeer naar de demopagina of gebruik de volgende ingesloten versie ervan na deze lijst.
  4. Klik op de bovenste knop met het label Taken regelmatig uitvoeren .
  5. Klik ten slotte op de knop met het label Run loop, yielding with scheduler.yield bij elke iteratie .

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 yields genereert met setTimeout , zie je dat de lus – hoewel deze na elke iteratie yields genereert – het resterende werk niet naar achteren, maar naar voren in de wachtrij stuurt. Dit biedt je het beste van twee werelden: je kunt yields genereren om de responsiviteit van de invoer op je website te verbeteren, maar er ook voor zorgen dat het werk dat je na yields wilde voltooien, geen vertraging oploopt.

Probeer het eens!

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

  1. Als u lokaal wilt experimenteren met scheduler.yield , typt en 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 alle andere experimentele functies) alleen beschikbaar in uw Chrome-instantie.
  2. Als u scheduler.yield wilt inschakelen voor echte Chromium-gebruikers op een openbaar toegankelijke oorsprong, moet u zich aanmelden voor de scheduler.yield -oorsprongproefversie . Hiermee kunt u veilig experimenteren met voorgestelde functies gedurende een bepaalde periode en krijgt het Chrome-team waardevolle inzichten in hoe deze functies in de praktijk worden gebruikt. Lees deze handleiding voor meer informatie over hoe oorsprongproeven werken.

Hoe u scheduler.yield gebruikt – en tegelijkertijd browsers ondersteunt die het niet implementeren – hangt af van uw doelen. 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. U wilt prioriteiten kunnen stellen aan taken en opbrengsten.
  3. U wilt taken kunnen annuleren of opnieuw prioriteren met behulp van de TaskController klasse die de scheduler.postTask API biedt.

Als dit niet uw situatie beschrijft, is polyfill mogelijk niet geschikt voor u. In dat geval kunt u op verschillende manieren uw eigen fallback gebruiken. De eerste aanpak gebruikt scheduler.yield als deze beschikbaar is, maar valt terug op setTimeout als deze niet beschikbaar 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 al vermoedt, zullen browsers die scheduler.yield niet ondersteunen, wel een 'vooraan in de wachtrij'-functie gebruiken. Als dat betekent dat je liever helemaal geen 'vooraan in de wachtrij'-functie gebruikt, kun je een andere aanpak proberen die scheduler.yield gebruikt als die beschikbaar is, maar helemaal geen 'vooraan in de wachtrij'-functie gebruikt als die niet beschikbaar 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 interessante toevoeging aan de scheduler API – een toevoeging die het hopelijk voor ontwikkelaars makkelijker maakt om de responsiviteit te verbeteren dan met de huidige yield-strategieën. Lijkt scheduler.yield u een nuttige API? Doe dan mee aan ons onderzoek om de API te verbeteren en geef feedback over hoe deze verder verbeterd kan worden.

Heldenafbeelding van Unsplash , door Jonathan Allison .