Skip to main content

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

NameTypeDescription
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