Cooperative scheduler for the browser environment that provides time-slicing capabilities for JavaScript applications.
—
Utilities for controlling when the scheduler yields control to the browser and managing frame rates for optimal performance.
Determines whether the scheduler should yield control back to the browser's main thread.
/**
* Check if the scheduler should yield control to the browser
* @returns true if the scheduler should yield, false otherwise
*/
function unstable_shouldYield(): boolean;This function helps implement cooperative scheduling by indicating when the current time slice has been exhausted. Tasks should check this periodically during long-running operations.
Usage Examples:
import { unstable_shouldYield } from "scheduler";
// Process large dataset with yielding
function processLargeArray(items) {
const results = [];
for (let i = 0; i < items.length; i++) {
// Process current item
results.push(processItem(items[i]));
// Yield if we've used up our time slice
if (unstable_shouldYield()) {
// Return continuation to finish remaining work
return () => {
return processLargeArray(items.slice(i + 1));
};
}
}
return results; // All work completed
}
// Use within scheduled callback
import { unstable_scheduleCallback, unstable_NormalPriority } from "scheduler";
unstable_scheduleCallback(unstable_NormalPriority, function processWork(didTimeout) {
let workCompleted = 0;
while (workQueue.length > 0 && !unstable_shouldYield()) {
const workItem = workQueue.shift();
performWork(workItem);
workCompleted++;
}
console.log(`Completed ${workCompleted} items this slice`);
// Continue if more work remains
if (workQueue.length > 0) {
return processWork;
}
});Returns the current high-resolution timestamp, similar to performance.now().
/**
* Get current high-resolution timestamp
* @returns Current time in milliseconds with sub-millisecond precision
*/
function unstable_now(): number;Usage Examples:
import { unstable_now } from "scheduler";
// Measure execution time
const startTime = unstable_now();
performExpensiveOperation();
const endTime = unstable_now();
console.log(`Operation took ${endTime - startTime}ms`);
// Implement custom timeout logic
function processWithTimeout(maxDuration) {
const startTime = unstable_now();
while (hasMoreWork()) {
processWorkItem();
// Check if we've exceeded our time budget
if (unstable_now() - startTime > maxDuration) {
console.log("Timeout reached, yielding");
break;
}
}
}
// Track performance metrics
const performanceMetrics = {
taskStartTime: null,
startTask() {
this.taskStartTime = unstable_now();
},
endTask() {
const duration = unstable_now() - this.taskStartTime;
console.log(`Task completed in ${duration}ms`);
}
};Signals to the scheduler that a paint is needed, which may influence yielding decisions.
/**
* Request that the browser paint soon
* May influence the scheduler's yielding behavior
*/
function unstable_requestPaint(): void;Usage Examples:
import { unstable_requestPaint, unstable_scheduleCallback, unstable_NormalPriority } from "scheduler";
// Request paint after visual updates
function updateMultipleElements() {
unstable_scheduleCallback(unstable_NormalPriority, () => {
// Update DOM elements
updateElement1();
updateElement2();
updateElement3();
// Signal that visual changes were made
unstable_requestPaint();
});
}
// Use in animation loops
function animationStep() {
unstable_scheduleCallback(unstable_NormalPriority, function animate(didTimeout) {
// Update animation state
updateAnimationFrame();
// Request paint to show changes
unstable_requestPaint();
// Continue animation if not complete
if (animationRunning) {
return animate;
}
});
}
// Batch DOM updates and request paint
function batchDOMUpdates(updates) {
unstable_scheduleCallback(unstable_NormalPriority, () => {
updates.forEach(update => {
applyDOMUpdate(update);
});
// All DOM changes complete, request paint
unstable_requestPaint();
});
}Forces the scheduler to target a specific frame rate by adjusting the yielding interval.
/**
* Force scheduler to target a specific frame rate
* @param fps - Target frames per second (0-125), or 0 to reset to default
*/
function unstable_forceFrameRate(fps: number): void;Usage Examples:
import { unstable_forceFrameRate } from "scheduler";
// Set 30 FPS for performance-constrained environments
unstable_forceFrameRate(30);
// Set 60 FPS for smooth animations
unstable_forceFrameRate(60);
// Reset to default frame rate (browser-dependent, typically 60 FPS)
unstable_forceFrameRate(0);
// Adaptive frame rate based on device capabilities
function setAdaptiveFrameRate() {
const isLowEndDevice = navigator.hardwareConcurrency <= 2;
const targetFPS = isLowEndDevice ? 30 : 60;
unstable_forceFrameRate(targetFPS);
console.log(`Frame rate set to ${targetFPS} FPS`);
}
// Frame rate for specific scenarios
function enterGameMode() {
unstable_forceFrameRate(60); // Smooth gaming experience
}
function enterPowerSaveMode() {
unstable_forceFrameRate(15); // Conserve battery
}
function exitSpecialMode() {
unstable_forceFrameRate(0); // Reset to default
}The scheduler's yielding behavior is controlled by several factors:
unstable_forceFrameRateunstable_requestPaint may cause earlier yielding// Good: Check shouldYield in loops
function processItems(items) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
if (unstable_shouldYield()) {
// Yield and continue with remaining items
return () => processItems(items.slice(i + 1));
}
}
}
// Good: Use requestPaint after visual updates
function updateVisuals() {
updateDOM();
unstable_requestPaint(); // Ensure changes are painted
}
// Avoid: Not checking shouldYield in long operations
function badProcessItems(items) {
// This blocks the main thread
for (let i = 0; i < items.length; i++) {
processItem(items[i]); // No yielding check
}
}The scheduler integrates with various browser timing APIs:
performance.now(): Used internally by unstable_now()MessageChannel: Primary scheduling mechanism in browserssetTimeout: Fallback scheduling mechanismsetImmediate: Used in Node.js environments when availableInstall with Tessl CLI
npx tessl i tessl/npm-scheduler