Leverage scheduler.yield in splitTask when available#66001
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @brendankenny, @LeszekSwirski. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| return new Promise( async ( resolve ) => { | ||
| if ( | ||
| 'scheduler' in window && | ||
| typeof window.scheduler === 'object' && | ||
| null !== window.scheduler && | ||
| 'yield' in window.scheduler && | ||
| typeof window.scheduler.yield === 'function' | ||
| ) { | ||
| await window.scheduler.yield(); | ||
| resolve( undefined ); | ||
| } else { | ||
| setTimeout( resolve, 0 ); | ||
| } | ||
| } ); |
There was a problem hiding this comment.
This is more readable IMO and avoids an async function in the promise body:
export const splitTask = () => {
if (
'scheduler' in window &&
typeof window.scheduler === 'object' &&
null !== window.scheduler &&
'yield' in window.scheduler &&
typeof window.scheduler.yield === 'function'
) {
return window.scheduler.yield();
}
return new Promise( ( resolve ) => {
setTimeout( resolve, 0 );
} );
}There was a problem hiding this comment.
Yes, perfect. I spaced on this. I got the same feedback from @LeszekSwirski.
There was a problem hiding this comment.
And improved yet further in 2516cd4 by removing the wrapper function around scheduler.yield(). So when scheduler.yield() is available, splitTask is just a reference to that function.
There was a problem hiding this comment.
There's probably zero difference in the end between:
window.scheduler.yield.bind( window.scheduler )And:
() => {
return window.scheduler.yield();
}Since in both cases a new function is involved. The former seems to be syntactic sugar for the latter.
There was a problem hiding this comment.
But why use bind over the latter? I find the latter more intuitive to understand. Also, I'm not familiar enough with bind to be confident so that is the same. Since you also say "probably", I'd suggest we go with the latter.
We have to use a function anyway for the fallback of returning a promise, so we might as well wrap the entire code in a function.
There was a problem hiding this comment.
"bind" is very very slightly preferable for the JS engine, since it can represent the result as a "bound function" that forwards directly to the function it binds already while resolving the call, and doesn't need to parse it, compile it, interpret bytecode, etc.
Co-authored-by: swissspidy <swissspidy@git.wordpress.org>
Co-authored-by: Brendan Kenny <bckenny@gmail.com>
Co-authored-by: Brendan Kenny <bckenny@gmail.com>
| }; | ||
| export const splitTask = | ||
| typeof window.scheduler?.yield === 'function' | ||
| ? window.scheduler.yield.bind( window.scheduler ) |
There was a problem hiding this comment.
In other places in Gutenberg, I believe we use requestIdleCallback to optimize for UI responsiveness and delay some tasks with lower priority. I wonder if you can speak about the difference and when to favor one over the other.
There was a problem hiding this comment.
With requestIdleCallback the idleness may occur several seconds after requested which would not be desirable for hydration. For splitTask it's purely to break up long tasks which remain high priority. So both have their use cases.
|
This PR may have had an impact on the lightbox. Please see #66691. |
* Leverage scheduler.yield in splitTask when available * Eliminate extra Promise when using scheduler.yield() Co-authored-by: swissspidy <swissspidy@git.wordpress.org> * Avoid needlessly re-checking whether scheduler.yield() is defined * Remove needless function wrapper * Ensure that scheduler is context object to yield function * Remove needless async * Reduce verbosity of scheduler.yield() existence check * Fix return type for scheduler.yield() Co-authored-by: Brendan Kenny <bckenny@gmail.com> * Define scheduler and scheduler.yield as optional Co-authored-by: Brendan Kenny <bckenny@gmail.com> --------- Unlinked contributors: brendankenny, LeszekSwirski. Co-authored-by: felixarntz <flixos90@git.wordpress.org>

Fixes #64483.
What?
Use
scheduler.yield()in thesplitTask()function in@wordpress/interactivitywhen available. Otherwise, continue to fall back to usingsetTimeoutas currently.Why?
In #58227 a
yieldToMain()function was introduced to break up long hydration tasks when a document is initialized by the Interactivity API. At the time, this function was implemented usingsetTimeout()as shown in the Optimize long tasks article on web.dev. That article also describes a more performant mechanism inscheduler.yield()which would be coming at a later date. That date is now in Chromium browsers (caniuse).In #62665 the
yieldToMain()function was renamed tosplitTask()and was then exported by the@wordpress/interactivitypackage for extensions to leverage (e.g. in Async Actions).On a post that contains 38 interactive blocks (36 of which are Image blocks with lightboxes) the performance benefits of using
scheduler.yield()in the following two traces (both run with 6x CPU slowdown on a HP Dragonfly Elite Chromebook):loadevent.Splitting tasks with
setTimeout()See trace.cafe.
Splitting tasks with
scheduler.yield()See trace.cafe
Additional Observation
When
scheduler.yield()is used, I see a 99ms long task caused byflushAfterPaintEffects:When
setTimeout()is used, these tasks are split up (where the black outlines indicate instances offlushAfterPaintEffects):This appears to be due to the fact that
flushAfterPaintEffectsitself is scheduled for execution using a timer, meaning that whenscheduler.yield()is used, the pending paint effects are allowed to bunch up for one single call. This is part of Preact, and perhaps that long task can be addressed upstream by introducingscheduler.yield()there as well (cf. react/react#27069).