scheduleMacrotaskSafe
Schedules a callback function to execute in the next macrotask cycle with environment-optimized timing.
Uses setImmediate (Node.js) or setTimeout (browsers) to ensure safe rendering in browsers.
This scheduling strategy guarantees that the browser has an opportunity to update the display
and process user interactions between task executions, preventing UI freezing and ensuring
smooth rendering performance.
Signature
const scheduleMacrotaskSafe: Fn<[callback: Fn<[], void>], number>
Parameters
| Name | Type | Description |
|---|---|---|
callback | - | Function to execute in the next macrotask cycle |
Returns
Numeric ID that can be used with cancelMacrotask to cancel execution
Examples
Basic macrotask scheduling
import { scheduleMacrotask } from '@winglet/common-utils';
console.log('1: Synchronous code');
// Schedule macrotask
const taskId = scheduleMacrotask(() => {
console.log('4: Macrotask executed');
});
// Schedule microtask for comparison
Promise.resolve().then(() => {
console.log('3: Microtask executed');
});
console.log('2: More synchronous code');
// Output order:
// 1: Synchronous code
// 2: More synchronous code
// 3: Microtask executed
// 4: Macrotask executed
Event loop execution order demonstration
const executionOrder: string[] = [];
// Synchronous execution
executionOrder.push('sync-1');
// Macrotask (runs after microtasks)
scheduleMacrotask(() => {
executionOrder.push('macrotask-1');
// Nested microtask (runs before next macrotask)
queueMicrotask(() => {
executionOrder.push('nested-microtask');
});
// Nested macrotask (runs in subsequent cycle)
scheduleMacrotask(() => {
executionOrder.push('nested-macrotask');
});
});
// Microtask (runs before macro tasks)
queueMicrotask(() => {
executionOrder.push('microtask-1');
});
// Another macrotask
scheduleMacrotask(() => {
executionOrder.push('macrotask-2');
});
executionOrder.push('sync-2');
// Final order: ['sync-1', 'sync-2', 'microtask-1', 'macrotask-1', 'macrotask-2', 'nested-microtask', 'nested-macrotask']
UI updates and rendering coordination
// Defer heavy computation to avoid blocking UI
function processLargeDataset(data: any[], callback: (result: any) => void) {
const batchSize = 1000;
let index = 0;
const results: any[] = [];
function processBatch() {
const end = Math.min(index + batchSize, data.length);
// Process batch synchronously
for (let i = index; i < end; i++) {
results.push(processItem(data[i]));
}
index = end;
if (index < data.length) {
// Schedule next batch to allow UI updates
scheduleMacrotask(processBatch);
} else {
callback(results);
}
}
scheduleMacrotask(processBatch);
}
// DOM manipulation coordination
function updateUIAfterDataChange(newData: any) {
// Update data model (synchronous)
updateModel(newData);
// Schedule DOM updates for next macrotask
scheduleMacrotask(() => {
updateDOM();
// Schedule analytics after DOM is updated
scheduleMacrotask(() => {
trackUserInteraction('data-updated');
});
});
}
Async task coordination
// Coordinate multiple async operations
async function coordinatedExecution() {
console.log('Starting coordination');
// Immediate microtask
await Promise.resolve();
console.log('After microtask');
// Schedule work for next macrotask
await new Promise(resolve => {
scheduleMacrotask(() => {
console.log('In macrotask');
resolve(undefined);
});
});
console.log('After macrotask');
}
// Testing and timing control
function createTestSequence() {
const events: string[] = [];
return {
addSyncEvent: (name: string) => events.push(`sync:${name}`),
addMicrotaskEvent: (name: string) => {
queueMicrotask(() => events.push(`micro:${name}`));
},
addMacrotaskEvent: (name: string) => {
scheduleMacrotask(() => events.push(`macro:${name}`));
},
getEvents: () => [...events]
};
}
Error handling and cleanup
// Safe macrotask scheduling with error handling
function safeScheduleMacrotask(callback: () => void, errorHandler?: (error: Error) => void) {
return scheduleMacrotask(() => {
try {
callback();
} catch (error) {
if (errorHandler) {
errorHandler(error as Error);
} else {
console.error('Macrotask error:', error);
}
}
});
}
// Resource cleanup coordination
function scheduleCleanup(resources: Resource[]) {
scheduleMacrotask(() => {
resources.forEach(resource => {
try {
resource.cleanup();
} catch (error) {
console.warn('Cleanup error:', error);
}
});
});
}
Playground
import { scheduleMacrotask } from '@winglet/common-utils'; console.log('1: Synchronous code'); // Schedule macrotask const taskId = scheduleMacrotask(() => { console.log('4: Macrotask executed'); }); // Schedule microtask for comparison Promise.resolve().then(() => { console.log('3: Microtask executed'); }); console.log('2: More synchronous code'); // Output order: // 1: Synchronous code // 2: More synchronous code // 3: Microtask executed // 4: Macrotask executed
Notes
Why This Approach Ensures Safe Rendering:
- Yielding Control: Both APIs yield control back to the browser's event loop
- Rendering Opportunity: Browser can perform layout, paint, and composite between tasks
- Responsive UI: User interactions are processed between scheduled callbacks
- Frame Budget: Respects the browser's 16ms frame budget for smooth 60fps rendering
Platform Behavior for Safe Rendering:
- Node.js (setImmediate): Executes in next event loop iteration, allowing I/O and rendering
- Browsers (setTimeout): Minimum 4ms delay provides guaranteed rendering window
- Background Tabs: Automatic throttling prevents resource waste while maintaining safety
- Mobile Browsers: Optimized scheduling respects battery and performance constraints
Performance Characteristics:
- Time Complexity: O(1) for scheduling operation
- Space Complexity: O(1) per scheduled task
- Scheduling Overhead: ~0.001ms in Node.js, ~0.01ms in browsers
- Memory Usage: Minimal, automatic cleanup after execution
Comparison with Alternatives:
- setTimeout(0): Similar but may have longer delays in browsers
- Promise.resolve().then(): Executes earlier (microtask queue)
- requestAnimationFrame: Tied to browser rendering, different timing
- MessageChannel: More complex setup, similar timing characteristics
Use Cases:
- Breaking up long-running computations to avoid blocking
- Coordinating DOM updates with data changes
- Implementing custom scheduling systems
- Deferring non-critical operations
- Testing event loop behavior
- Coordinating with external async operations