How to Test scheduler.yield(): Chrome’s API for Optimizing INP

Last updated on Mar 13th, 2024 | 6 min

TL;DR: Chrome's scheduler.yield() API promises to revolutionize site responsiveness by allowing developers to better manage task execution, ensuring smoother user interactions and improved Interaction to Next Paint (INP) scores. This approach offers a practical solution for breaking up long tasks without the drawbacks of existing methods, aiming for an optimal balance between task management and user experience.

There’s no doubt that Google is all about responsiveness in 2023. 

So far, they:

  • Moved Interaction to Next Paint from experimental to pending;
  • Announced that INP will replace First Input Delay as the new Core Web Vital metric for responsiveness in March 2024;
  • Started flagging INP issues in Search Console and sending emails to websites that miss the threshold for good responsiveness;
  • On March 12, 2024, INP officially replaced FID as the new responsiveness metric.

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. 

A Quick Recap on Long Tasks and The Main Thread

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.

Main Thread

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.

Long tasks before

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.

Long tasks after

Unfortunately, the current yielding strategies are not perfect…


Why scheduler.yield(): The Issue with Current Yielding Strategies

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:

1. setTimeout()

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.

2. requestIdleCallback()

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. 

3. isInputPending()

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:

  • Increased chance of logic errors: Since the deferred code is placed at the end of the task queue, there may be other tasks that the browser executes before getting back to the deferred task. This can affect the order in which functions are executed and potentially cause logic errors or unexpected behavior.
  • Delay in execution: If there are many tasks in the queue, it might take a significant amount of time before the browser reaches and executes the deferred code. 
  • Unpredictability: It's hard to predict precisely when the deferred task will run, as it depends on the number and nature of tasks already in the queue. This unpredictability can make debugging and performance optimization challenging.

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.

Introducing scheduler.yield()

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.

“Refactoring long tasks is not always straightforward. It's nice that the Chrome team is providing an ergonomic way of doing that. This is definitely a move in the right direction.”Ivailo Hristov, CTO of NitroPack.


How to Try The New Scheduler API

Starting in Chrome 115, you can test scheduler.yield on your own. 

To experiment with the new API, simply follow Google’s instructions:

  1. If you want to experiment with scheduler.yield locally, type and enter chrome://flags in Chrome's address bar and select Enable from the dropdown in the Experimental Web Platform Features section. This will make scheduler.yield (and any other experimental features) available in only your instance of Chrome.
  2. If you want to enable scheduler.yield for real Chromium users on a publicly accessible origin, you'll need to sign up for the scheduler.yield origin trial. This allows you to safely experiment with proposed features for a given period of time, and gives the Chrome Team valuable insights into how those features are used in the field. For more information on how origin trials work, read this guide.

Once you test it, you can also provide feedback on how it can be improved. 

Safe testing! 

How NitroPack Can Help with Unblocking The Main Thread

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. 

Improve your site’s responsiveness immediately. Get NitroPack for FREE →

Niko Kaleev
Web Performance Geek

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.