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.

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:
- 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
. - 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:
- Ga naar
chrome://flags
. - Schakel het experiment 'Experimentele webplatformfuncties' in. Mogelijk moet u Chrome hierna opnieuw opstarten.
- Navigeer naar de demopagina of gebruik de volgende ingesloten versie ervan na deze lijst.
- Klik op de bovenste knop met het label Taken regelmatig uitvoeren .
- 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:
- Als u lokaal wilt experimenteren met
scheduler.yield
, typt en typt uchrome://flags
in de adresbalk van Chrome en selecteert u Inschakelen in de vervolgkeuzelijst in het gedeelte Experimentele webplatformfuncties . Hierdoor wordtscheduler.yield
(en alle andere experimentele functies) alleen beschikbaar in uw Chrome-instantie. - Als u
scheduler.yield
wilt inschakelen voor echte Chromium-gebruikers op een openbaar toegankelijke oorsprong, moet u zich aanmelden voor descheduler.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:
- U gebruikt
scheduler.postTask
al in uw toepassing om taken te plannen. - U wilt prioriteiten kunnen stellen aan taken en opbrengsten.
- U wilt taken kunnen annuleren of opnieuw prioriteren met behulp van de
TaskController
klasse die descheduler.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 .