Defer Non-Critical Work with requestIdleCallback
Impact: MEDIUM (keeps UI responsive during background tasks)
Use requestIdleCallback() to schedule non-critical work during browser idle periods. This keeps the main thread free for user interactions and animations, reducing jank and improving perceived performance.
Incorrect (blocks main thread during user interaction):
function handleSearch(query: string) {
const results = searchItems(query)
setResults(results)
// These block the main thread immediately
analytics.track('search', { query })
saveToRecentSearches(query)
prefetchTopResults(results.slice(0, 3))
}Correct (defers non-critical work to idle time):
function handleSearch(query: string) {
const results = searchItems(query)
setResults(results)
// Defer non-critical work to idle periods
requestIdleCallback(() => {
analytics.track('search', { query })
})
requestIdleCallback(() => {
saveToRecentSearches(query)
})
requestIdleCallback(() => {
prefetchTopResults(results.slice(0, 3))
})
}With timeout for required work:
// Ensure analytics fires within 2 seconds even if browser stays busy
requestIdleCallback(
() => analytics.track('page_view', { path: location.pathname }),
{ timeout: 2000 }
)Chunking large tasks:
function processLargeDataset(items: Item[]) {
let index = 0
function processChunk(deadline: IdleDeadline) {
// Process items while we have idle time (aim for <50ms chunks)
while (index < items.length && deadline.timeRemaining() > 0) {
processItem(items[index])
index++
}
// Schedule next chunk if more items remain
if (index < items.length) {
requestIdleCallback(processChunk)
}
}
requestIdleCallback(processChunk)
}With fallback for unsupported browsers:
const scheduleIdleWork = window.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1))
scheduleIdleWork(() => {
// Non-critical work
})When to use:
- Analytics and telemetry
- Saving state to localStorage/IndexedDB
- Prefetching resources for likely next actions
- Processing non-urgent data transformations
- Lazy initialization of non-critical features
When NOT to use:
- User-initiated actions that need immediate feedback
- Rendering updates the user is waiting for
- Time-sensitive operations