There’s no doubt that Google is all about responsiveness in 2023.
So far, they:
Now, the Chrome Team announced they are currently running an origin trial for a new scheduler API – scheduler.yield().
scheduler.yield() is expected to help developers improve their sites’ responsiveness by providing them with an easier and better way to yield control back to the main thread.
Read on to learn more about the new API and how to try it on your website.
If you are well aware of what long tasks and the main thread are, feel free to skip this part. If not, we encourage you to read this quick recap as it’s fundamental for understanding scheduler.yield() and how to implement it.
Everything that the browser does as work is considered a task. This includes rendering, parsing HTML and CSS, running the JavaScript code you write, and other things you may not have direct control over.
The main thread is where the browser does most of the work.
Unfortunately, the main thread can process only one task at a time. And if a task takes more than 50ms to run, it’s considered a long task.
Encountering a long task means the browser will run it as long as necessary to complete it. Once finished, control is yielded back to the main thread, allowing the browser to process the next task in the queue.
Long tasks are the primary source of poor page responsiveness because they delay the browser's ability to respond to user input. Furthermore, JavaScript, with its run-to-completion model, is the main culprit when it comes to blocking the main thread.
That’s why it’s considered a render-blocking resource – when the browser encounters it, it must download, parse, and execute it before doing anything else.
The good news is that just because your code kicks off a task in the browser doesn't mean you have to wait until that task is finished before control is returned to the main thread.
You can break up long tasks by yielding explicitly in a task.
In simpler terms, task yielding ensures that the browser doesn’t get so caught up in one task that it misses or delays responding to other important tasks or user interactions.
Unfortunately, the current yielding strategies are not perfect…
Yielding to the main thread isn’t a new concept. Developers have been using different yielding strategies to break up long tasks for quite some time:
setTimeout() allows you to schedule a task to run after a specified delay or at regular intervals. This postpones the execution of the callback into a separate task, even if you specify a timeout of 0. This method is effective when you have multiple functions that should run one after the other.
Drawback: Precision is not guaranteed. The callback might not run exactly after the specified delay due to other tasks in the queue. Also, if you're processing a vast dataset in a loop, the task could become time-consuming, especially with millions of entries.
requestIdleCallback() allows you to schedule a task to run during any idle periods the browser might have. It's useful for performing non-urgent tasks without impacting the user experience.
Drawback: requestIdleCallback() schedules tasks at the lowest possible priority, meaning that if the main thread is congested, scheduled tasks may never get to run.
isInputPending() can be executed anytime to check if a user is trying to engage with an element on the page. If they are, the function returns true; if not, it returns false.
Imagine you have a lineup of tasks to execute but don't want to disrupt user interactions. You can use isInputPending() and the yieldToMain() function to ensure user input isn't delayed as they interact with the page.
Drawback: isInputPending() may not always return true immediately after user input. This is because it takes time for the operating system to tell the browser that the interaction occurred. This means that other code may have already started executing.
These are some of the popular ways to yield back to the main thread. As you can see, each one has its own drawbacks.
But the most significant downside is that:
When you yield to the main thread by deferring code to run in a subsequent task, that code gets added to the very end of the task queue.
Why is that an issue?
It’s a three-fold answer:
In summary, while using the current strategies to yield to the main thread can help maintain a responsive user interface, it can also introduce challenges in ensuring the timely and orderly execution of code.
The excitement about Chrome running an origin trial for scheduler.yield() is because it's a scheduler API that addresses all the drawbacks of the other yielding strategies.
On top of that, it’s a solution that will enable both developers and owners to achieve responsive websites and good INP scores while seamlessly executing the rest of the code.
So what’s all the hype about scheduler.yield()?
For starters, scheduler.yield() is a dedicated yield function. setTimeout(), for instance, is used to break up long tasks and yield to the main thread, but it’s more of a function side effect than a default option.
Secondly, scheduler.yield() sends the remaining work to the front of the queue. This means that work you want to resume immediately after yielding won't take a back seat to tasks from other sources.
Put simply:
scheduler.yield() gives you the best of both worlds – you can yield to improve your site’s responsiveness and INP score and ensure that the work you wanted to finish after yielding isn't delayed.
Starting in Chrome 115, you can test scheduler.yield on your own.
To experiment with the new API, simply follow Google’s instructions:
Once you test it, you can also provide feedback on how it can be improved.
Safe testing!
Breaking long tasks into smaller chunks is essential for providing users with a snappy experience.
But wouldn’t it be better if you could preemptively optimize some of the heavy JavaScript?
That’s where NitroPack comes in.
With its 35+ advanced web performance features, NitroPack helps 180,000+ websites globally achieve an excellent user experience, Core Web Vitals, and conversion rates.
One of NitroPack’s most significant advantages is its way of handling JavaScript execution.
Upon installing NitroPack, our service delays the loading of non-critical resources until user interaction is detected.
Moreover, thanks to our proprietary resource loading mechanism, NitroPack can rearrange how resources are fed to the main thread. We do this to take advantage of the modern CPU’s multi-core nature by offloading tasks away from the main thread.
This way, we can guarantee that your main thread stays unblocked and available to handle user interactions.
Niko has 5+ years of experience turning those “it’s too technical for me” topics into “I can’t believe I get it” content pieces. He specializes in dissecting nuanced topics like Core Web Vitals, web performance metrics, and site speed optimization techniques. When he’s taking a breather from researching his next content piece, you’ll find him deep into the latest performance news.