Het bouwen van websites die snel reageren op gebruikersinvoer is een van de grootste uitdagingen op het gebied van webprestaties – een uitdaging waar het Chrome-team hard aan werkt om webontwikkelaars bij te helpen. Dit jaar werd aangekondigd dat de Interaction to Next Paint (INP)-metriek de status van experimenteel zou krijgen en in afwachting van implementatie. Het is nu de bedoeling dat deze in maart 2024 de First Input Delay (FID) vervangt als een essentiële webmetriek.
In een voortdurende poging om nieuwe API's te leveren die webontwikkelaars helpen hun websites zo snel mogelijk te maken, voert het Chrome-team momenteel een origin-test uit voor scheduler.yield , beginnend in versie 115 van Chrome. scheduler.yield is een voorgestelde nieuwe toevoeging aan de scheduler-API die een eenvoudigere en betere manier biedt om de controle terug te geven aan de hoofdthread dan de methoden die traditioneel werden gebruikt .
Bij het opgeven
JavaScript gebruikt het 'run-to-completion'-model om taken af te handelen. Dit betekent dat wanneer een taak op de hoofdthread wordt uitgevoerd, deze taak zo lang duurt als nodig is om te worden voltooid. Zodra een taak is voltooid, wordt de controle teruggegeven aan de hoofdthread, waardoor deze de volgende taak in de wachtrij kan verwerken.
Afgezien van extreme gevallen waarin een taak nooit wordt voltooid – zoals bijvoorbeeld een oneindige lus – is het teruggeven van taken een onvermijdelijk onderdeel van de taakplanningslogica van JavaScript. Het zal gebeuren, het is alleen een kwestie van wanneer , en hoe eerder hoe beter. Wanneer taken te lang duren – langer dan 50 milliseconden om precies te zijn – worden ze beschouwd als lange taken .
Langdurige taken leiden tot een trage respons van de pagina, omdat ze de browser vertragen bij het verwerken van gebruikersinvoer. Hoe vaker langdurige 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.
Het feit dat je code een taak in de browser start, betekent echter niet dat je moet wachten tot die taak is voltooid voordat de controle wordt teruggegeven aan de hoofdthread. Je kunt de responsiviteit op gebruikersinvoer op een pagina verbeteren door expliciet een taak terug te geven aan de hoofdthread (yielding). Hierdoor wordt de taak opgesplitst en op het eerstvolgende beschikbare moment voltooid. Dit geeft andere taken eerder toegang tot de hoofdthread dan wanneer ze zouden moeten wachten tot langdurige taken zijn afgerond.

Wanneer je expliciet de controle overdraagt, zeg je tegen de browser: "Hé, ik begrijp dat het werk dat ik ga doen even kan duren, en ik wil niet dat je al dat werk moet doen voordat je kunt reageren op gebruikersinvoer of andere taken die ook belangrijk kunnen zijn." Het is een waardevol hulpmiddel voor ontwikkelaars dat de gebruikerservaring aanzienlijk kan verbeteren.
Het probleem met de huidige rendementsstrategieën
Een veelgebruikte methode om taken over te dragen maakt gebruik van setTimeout met een time-outwaarde van 0 Dit werkt omdat de callback die aan setTimeout wordt doorgegeven, de resterende taken naar een aparte taak verplaatst die in de wachtrij wordt geplaatst voor latere uitvoering. In plaats van te wachten tot de browser zelf taken overdraagt, zeg je eigenlijk: "verdeel dit grote stuk werk in kleinere delen".
Het gebruik van setTimeout om taken uit te voeren, heeft echter een potentieel ongewenst neveneffect: de taken die na het uitstelpunt worden uitgevoerd, komen achteraan in de wachtrij terecht. Taken die door gebruikersinteracties worden gepland, komen wel vooraan in de wachtrij te staan, zoals het hoort, maar de resterende taken die u na het expliciet uitstellen van taken wilde uitvoeren, kunnen verder worden vertraagd door andere taken van concurrerende bronnen die vóór u in de wachtrij stonden.
Om dit in actie te zien, probeer deze Codepen-demo uit — of experimenteer ermee in de onderstaande ingesloten versie. De demo bestaat uit een paar knoppen waarop je kunt klikken, en een vak eronder dat registreert wanneer taken worden uitgevoerd. Voer de volgende acties uit wanneer je op de pagina terechtkomt:
- Klik op de bovenste knop met het label ' Taken periodiek uitvoeren' . Hiermee worden blokkerende taken gepland om met een bepaalde frequentie te worden uitgevoerd. Wanneer u op deze knop klikt, wordt het taaklogboek gevuld met verschillende berichten zoals 'Blokkerende taak uitgevoerd met
setInterval. - Klik vervolgens op de knop met het label "Run loop", waarbij
setTimeoutwordt ingesteld voor elke iteratie .
Je 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 teruggeven van de taak met setTimeout . De lus die wordt uitgevoerd, verwerkt vijf items en geeft de taak terug 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 taken uitvoert met een bepaald interval. Het "einde van de taakwachtrij"-gedrag dat optreedt bij het teruggeven van de taak aan setTimeout betekent dat taken van andere bronnen in de wachtrij terecht kunnen komen vóór de resterende taken die de lus moet uitvoeren na het teruggeven van de taak.
Afhankelijk van je toepassing is dit misschien wel of niet een wenselijk resultaat, maar in veel gevallen is dit gedrag de reden waarom ontwikkelaars aarzelen om de controle over de hoofdthread zo snel uit handen te geven. Yielding is goed omdat gebruikersinteracties daardoor eerder kunnen worden uitgevoerd, maar het geeft ook andere taken die geen gebruikersinteractie vereisen de mogelijkheid om tijd op de hoofdthread door te brengen. Het is een echt 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 misschien hebt is: "Waarom heb ik een speciale functie nodig om `yield` te gebruiken als setTimeout dat al doet?"
Het is belangrijk om te weten dat het uitstellen van taken (yielding) geen ontwerpdoel was van setTimeout , maar eerder een prettig neveneffect van het inplannen van een callback die op een later tijdstip wordt uitgevoerd – zelfs met een opgegeven time-outwaarde van 0 Wat echter belangrijker is om te onthouden, is dat het uitstellen van taken met setTimeout de resterende taken naar het einde van de takenwachtrij stuurt. Standaard stuurt scheduler.yield de resterende taken naar het begin van de wachtrij. Dit betekent dat taken die je direct na het uitstellen van taken wilde hervatten, niet achtergesteld worden ten opzichte van taken uit andere bronnen (met de belangrijke uitzondering van gebruikersinteracties).
scheduler.yield is een functie die de controle overdraagt aan de hoofdthread en een Promise retourneert wanneer deze wordt aangeroepen. Dit betekent dat je er in een async functie op kunt wachten (` await ):
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
Om scheduler.yield in actie te zien, doe je het volgende:
- Ga naar
chrome://flags. - Schakel de experimentele functies van het webplatform in. Mogelijk moet u Chrome opnieuw opstarten nadat u dit hebt gedaan.
- Ga naar de demopagina of gebruik de onderstaande ingesloten versie ervan na deze lijst.
- Klik op de bovenste knop met het label ' Taken periodiek uitvoeren' .
- Klik ten slotte op de knop met het label "Run loop, yielding with
scheduler.yieldon each iteration" .
De uitvoer in het vak onderaan de pagina zal er ongeveer zo uitzien:
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 setTimeout gebruikt om de uitvoering te onderbreken, zie je dat de lus – hoewel deze na elke iteratie een pauze inlast – het resterende werk niet naar achteren in de wachtrij stuurt, maar juist naar voren. Dit biedt het beste van twee werelden: je kunt de uitvoering onderbreken om de responsiviteit van de invoer op je website te verbeteren, maar er tegelijkertijd voor zorgen dat het werk dat je na de pauze wilde afmaken, niet wordt vertraagd.
Probeer het eens!
Als scheduler.yield je interessant lijkt en je het wilt uitproberen, kun je dat vanaf versie 115 van Chrome op twee manieren doen:
- Als je lokaal met
scheduler.yieldwilt experimenteren, typ danchrome://flagsin de adresbalk van Chrome en selecteer 'Inschakelen' in het vervolgkeuzemenu onder ' Experimentele webplatformfuncties' . Hierdoor isscheduler.yield(en alle andere experimentele functies) alleen beschikbaar in jouw Chrome-installatie. - Als je
scheduler.yieldwilt inschakelen voor echte Chromium-gebruikers op een publiek toegankelijke origin, moet je je aanmelden voor descheduler.yieldorigin-proef . Hiermee kun je gedurende een bepaalde periode veilig experimenteren met voorgestelde functies en krijgt het Chrome-team waardevolle inzichten in hoe die functies in de praktijk worden gebruikt. Lees deze handleiding voor meer informatie over hoe origin-proeven werken.
Hoe je scheduler.yield gebruikt – en tegelijkertijd browsers ondersteunt die het niet zelf implementeren – hangt af van je doelstellingen. Je kunt de officiële polyfill gebruiken. De polyfill is nuttig als het volgende op jouw situatie van toepassing is:
- Je gebruikt in je applicatie al
scheduler.postTaskom taken in te plannen. - Je wilt taken en opbrengstprioriteiten kunnen instellen.
- Je wilt taken kunnen annuleren of de prioriteit ervan wijzigen met behulp van de
TaskControllerklasse die descheduler.postTaskAPI biedt.
Als dit niet op jouw situatie van toepassing is, is de polyfill wellicht niet geschikt voor jou. In dat geval kun je op een paar manieren zelf een fallback implementeren. De eerste methode maakt gebruik van scheduler.yield als dat beschikbaar is, maar valt terug op setTimeout als dat 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 wellicht al vermoedt, zullen browsers die scheduler.yield niet ondersteunen, de uitvoering stoppen zonder dat de gebruiker vooraan in de wachtrij komt te staan. Als je liever helemaal geen uitvoering stopt, kun je een andere aanpak proberen die scheduler.yield gebruikt als dat beschikbaar is, maar helemaal geen uitvoering stopt 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 spannende toevoeging aan de scheduler-API – hopelijk maakt dit het voor ontwikkelaars gemakkelijker om de responsiviteit te verbeteren dan met de huidige `yield`-strategieën. Als scheduler.yield u een nuttige API lijkt, nodigen we u van harte uit om deel te nemen aan ons onderzoek om de API te verbeteren en feedback te geven over hoe deze verder geoptimaliseerd kan worden.
Hoofdafbeelding afkomstig van Unsplash , gemaakt door Jonathan Allison .