Gepubliceerd: 6 maart 2025
Een pagina voelt traag en reageert niet goed wanneer lange taken de hoofdthread bezig houden en deze verhindert om ander belangrijk werk te doen, zoals het reageren op gebruikersinvoer. Hierdoor kunnen zelfs ingebouwde formulierbesturingselementen voor gebruikers kapot lijken – alsof de pagina vastgelopen is – laat staan complexere, aangepaste componenten.
scheduler.yield()
is een manier om de hoofdthread te ondersteunen, waardoor de browser al het werk met hoge prioriteit kan uitvoeren en de uitvoering kan voortzetten waar deze was gebleven. Dit zorgt ervoor dat een pagina responsiever blijft en draagt op zijn beurt bij aan een betere Interaction to Next Paint (INP) .
scheduler.yield
biedt een ergonomische API die precies doet wat hij belooft: de uitvoering van de functie die wordt aangeroepen, pauzeert bij de await scheduler.yield()
expressie en geeft deze door aan de hoofdthread, waardoor de taak wordt afgebroken. De uitvoering van de rest van de functie – de zogenaamde voortzetting van de functie – wordt gepland voor een nieuwe event-loop-taak.
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
Het specifieke voordeel van scheduler.yield
is dat de voortzetting na de yield wordt gepland vóór de uitvoering van andere soortgelijke taken die door de pagina in de wachtrij zijn geplaatst. Het geeft prioriteit aan het voortzetten van een taak boven het starten van nieuwe taken.
Functies als setTimeout
of scheduler.postTask
kunnen ook worden gebruikt om taken op te splitsen, maar deze voortzettingen worden doorgaans uitgevoerd na reeds in de wachtrij geplaatste nieuwe taken, waardoor er mogelijk lange vertragingen kunnen ontstaan tussen het overdragen aan de hoofdthread en het voltooien van de taken.
Geprioriteerde voortzettingen na het opgeven
scheduler.yield
maakt deel uit van de API voor prioritaire taakplanning . Als webontwikkelaars bespreken we doorgaans niet de volgorde waarin de event loop taken uitvoert in termen van expliciete prioriteiten, maar de relatieve prioriteiten zijn er altijd , zoals een requestIdleCallback
-callback die wordt uitgevoerd na alle setTimeout
-callbacks in de wachtrij, of een getriggerde invoergebeurtenislistener die meestal wordt uitgevoerd vóór een taak die in de wachtrij is geplaatst met setTimeout(callback, 0)
.
Met geprioriteerde takenplanning wordt dit explicieter gemaakt, waardoor het eenvoudiger wordt om te bepalen welke taak vóór een andere wordt uitgevoerd. Bovendien kunt u de prioriteiten aanpassen om de uitvoeringsvolgorde indien nodig te wijzigen.
Zoals vermeld, krijgt het voortzetten van een functie na het uitvoeren van een 'yield' met scheduler.yield()
een hogere prioriteit dan het starten van andere taken. Het uitgangspunt is dat de voortzetting van een taak eerst moet worden uitgevoerd, voordat andere taken worden gestart. Als de taak goed gedragende code is die periodiek 'yield' uitvoert zodat de browser andere belangrijke taken kan uitvoeren (zoals reageren op gebruikersinvoer), mag deze niet worden bestraft voor het 'yield' door voorrang te krijgen na andere, vergelijkbare taken.
Hier is een voorbeeld: twee functies, in de wachtrij geplaatst om in verschillende taken te worden uitgevoerd met behulp van setTimeout
.
setTimeout(myJob);
setTimeout(someoneElsesJob);
In dit geval staan de twee setTimeout
-aanroepen direct naast elkaar, maar op een echte pagina kunnen ze op compleet verschillende plekken worden aangeroepen. Bijvoorbeeld door een script van de eerste partij en een script van de derde partij die onafhankelijk van elkaar werk instellen om uit te voeren. Het kunnen ook twee taken van afzonderlijke componenten zijn die diep in de scheduler van uw framework worden geactiveerd.
Zo zou dat werk er in DevTools uit kunnen zien:
myJob
is gemarkeerd als een lange taak, waardoor de browser niets anders kan doen terwijl de taak actief is. Ervan uitgaande dat het afkomstig is van een first-party script, kunnen we het opsplitsen:
function myJob() {
// Run part 1.
myJobPart1();
// Yield with setTimeout() to break up long task, then run part2.
setTimeout(myJobPart2, 0);
}
Omdat myJobPart2
gepland is om te worden uitgevoerd met setTimeout
in myJob
, maar die planning wordt uitgevoerd nadat someoneElsesJob
al is gepland, ziet de uitvoering er als volgt uit:
We hebben de taak opgedeeld met setTimeout
, zodat de browser responsief kan zijn tijdens het midden van myJob
, maar nu wordt het tweede deel van myJob
pas uitgevoerd nadat someoneElsesJob
klaar is.
In sommige gevallen is dat prima, maar meestal is het niet optimaal. myJob
gaf voorrang aan de hoofdthread om ervoor te zorgen dat de pagina responsief bleef op gebruikersinvoer, en niet om de hoofdthread volledig op te geven. In gevallen waarin someoneElsesJob
bijzonder traag is, of waarin naast someoneElsesJob
ook veel andere taken zijn gepland, kan het lang duren voordat de tweede helft van myJob
wordt uitgevoerd. Dat was waarschijnlijk niet de bedoeling van de ontwikkelaar toen ze die setTimeout
aan myJob
toevoegden.
Voer scheduler.yield()
in, waardoor de voortzetting van elke functie die deze aanroept in een wachtrij met een iets hogere prioriteit wordt geplaatst dan het starten van andere vergelijkbare taken. Als myJob
wordt gewijzigd om deze functie te gebruiken:
async function myJob() {
// Run part 1.
myJobPart1();
// Yield with scheduler.yield() to break up long task, then run part2.
await scheduler.yield();
myJobPart2();
}
De uitvoering ziet er nu als volgt uit:
De browser kan nog steeds responsief zijn, maar de voortzetting van de myJob
-taak krijgt nu voorrang boven het starten van de nieuwe taak someoneElsesJob
. myJob
is dus voltooid voordat someoneElsesJob
begint. Dit komt veel dichter in de buurt van de verwachting om de responsiviteit te behouden door de hoofdthread te laten meedraaien, in plaats van de hoofdthread volledig op te geven.
Prioritaire overerving
Als onderdeel van de grotere API voor prioritaire taakplanning (Prioritized Task Scheduling API) werkt scheduler.yield()
goed samen met de expliciete prioriteiten die beschikbaar zijn in scheduler.postTask()
. Zonder expliciet ingestelde prioriteit gedraagt een scheduler.yield()
binnen een scheduler.postTask()
-callback zich in principe hetzelfde als in het vorige voorbeeld.
Als er echter een prioriteit is ingesteld, bijvoorbeeld een lage 'background'
prioriteit:
async function lowPriorityJob() {
part1();
await scheduler.yield();
part2();
}
scheduler.postTask(lowPriorityJob, {priority: 'background'});
De voortzetting wordt gepland met een prioriteit die hoger is dan andere 'background'
. De verwachte prioriteit voor de voortzetting ligt vóór alle in behandeling zijnde 'background'
. De prioriteit is echter nog steeds lager dan andere standaardtaken of taken met een hoge prioriteit. Het blijft 'background'
.
Dit betekent dat als u werk met lage prioriteit plant met een 'background'
scheduler.postTask()
(of met requestIdleCallback
), de voortzetting na een scheduler.yield()
daarin ook wacht totdat de meeste andere taken voltooid zijn en de hoofdthread inactief is om te worden uitgevoerd. Dit is precies wat u wilt van het opleveren van een taak met lage prioriteit.
Hoe de API te gebruiken
Momenteel is scheduler.yield()
alleen beschikbaar in Chromium-gebaseerde browsers. Om het te gebruiken moet u de functie detecteren gebruiken en voor andere browsers terugvallen op een secundaire manier van yielden.
scheduler-polyfill
is een kleine polyfill voor scheduler.postTask
en scheduler.yield
die intern een combinatie van methoden gebruikt om veel van de kracht van de planning-API's in andere browsers na te bootsen (hoewel scheduler.yield()
prioriteitsovererving niet wordt ondersteund).
Voor degenen die een polyfill willen vermijden, is een methode om via setTimeout()
te yielden en het verlies van een geprioriteerde voortzetting te accepteren, of zelfs helemaal geen yield te geven in niet-ondersteunde browsers als dat niet acceptabel is. Zie de documentatie van scheduler.yield()
in Optimaliseer lange taken voor meer informatie .
De wicg-task-scheduling
typen kunnen ook worden gebruikt om typecontrole en IDE-ondersteuning te krijgen als u scheduler.yield()
gebruikt voor functiedetectie en zelf een fallback toevoegt.
Meer informatie
Voor meer informatie over de API en hoe deze samenwerkt met taakprioriteiten en scheduler.postTask()
raadpleegt u de documentatie over scheduler.yield()
en Prioritized Task Scheduling op MDN.
Als u meer wilt weten over lange taken, hoe ze de gebruikerservaring beïnvloeden en wat u eraan kunt doen, lees dan het artikel over het optimaliseren van lange taken .