Skip to main content

scheduleNextTick

Schedules a function to execute on the next event loop tick with platform-optimized timing. Defers execution to the next iteration of the event loop, ensuring the task runs after current I/O events and microtasks but before subsequent I/O polling. Automatically selects the most appropriate scheduling mechanism for the runtime environment, providing optimal performance for server-side applications and consistent behavior across platforms.

Signature

const scheduleNextTick: Fn<[task: Fn<[], void>], void>

Parameters

NameTypeDescription
task-Function to execute on the next event loop tick

Examples

Basic next tick scheduling

import { scheduleNextTick } from '@winglet/common-utils';

console.log('1: Synchronous code');

// Schedule for next tick (after microtasks and current I/O)
scheduleNextTick(() => {
console.log('5: Next tick executed');
});

// Microtask (executes before next tick)
queueMicrotask(() => {
console.log('3: Microtask executed');
});

// Promise (also microtask)
Promise.resolve().then(() => {
console.log('4: Promise resolved');
});

console.log('2: More synchronous code');

// Output order:
// 1: Synchronous code
// 2: More synchronous code
// 3: Microtask executed
// 4: Promise resolved
// 5: Next tick executed

I/O coordination in Node.js

// Server-side I/O coordination
const fs = require('fs');

function readFileWithNextTick(filename: string, callback: (data: string) => void) {
fs.readFile(filename, 'utf8', (err: any, data: string) => {
if (err) throw err;

// Process data after current I/O phase
scheduleNextTick(() => {
const processedData = data.toUpperCase();
callback(processedData);
});
});
}

// Database operation coordination
function processDbResults(query: string, results: any[]) {
console.log('Processing database results');

// Defer heavy processing to next tick
scheduleNextTick(() => {
const processed = results.map(result => ({
...result,
processed: true,
timestamp: Date.now()
}));

console.log(`Processed ${processed.length} results`);
});
}

Event loop coordination patterns

// Coordinate multiple async phases
async function coordinateEventLoop() {
const phases: string[] = [];

phases.push('sync-start');

// Microtask phase
queueMicrotask(() => {
phases.push('microtask-1');
});

// Promise microtask
Promise.resolve().then(() => {
phases.push('promise-microtask');
});

// Next tick (after microtasks)
scheduleNextTick(() => {
phases.push('next-tick');

// Nested next tick
scheduleNextTick(() => {
phases.push('nested-next-tick');
});
});

// Another next tick
scheduleNextTick(() => {
phases.push('next-tick-2');
});

phases.push('sync-end');

// Wait for all phases to complete
await new Promise(resolve => setTimeout(resolve, 10));

console.log(phases);
// ['sync-start', 'sync-end', 'microtask-1', 'promise-microtask', 'next-tick', 'next-tick-2', 'nested-next-tick']
}

Resource cleanup and lifecycle management

// Server resource cleanup
class ServerResource {
private connections: Set<any> = new Set();
private cleanupScheduled = false;

addConnection(conn: any) {
this.connections.add(conn);
}

removeConnection(conn: any) {
this.connections.delete(conn);
this.scheduleCleanup();
}

private scheduleCleanup() {
if (!this.cleanupScheduled) {
this.cleanupScheduled = true;

// Cleanup after current I/O operations
scheduleNextTick(() => {
this.performCleanup();
this.cleanupScheduled = false;
});
}
}

private performCleanup() {
// Remove dead connections
for (const conn of this.connections) {
if (conn.destroyed) {
this.connections.delete(conn);
}
}
}
}

// Application lifecycle coordination
class ApplicationLifecycle {
private shutdownCallbacks: (() => void)[] = [];

onShutdown(callback: () => void) {
this.shutdownCallbacks.push(callback);
}

shutdown() {
console.log('Starting graceful shutdown');

// Execute shutdown callbacks in next tick
scheduleNextTick(() => {
this.shutdownCallbacks.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Shutdown callback error:', error);
}
});

// Final cleanup in next tick
scheduleNextTick(() => {
console.log('Graceful shutdown complete');
process.exit(0);
});
});
}
}

Testing and development utilities

// Flush all pending next tick operations
function flushNextTick(): Promise<void> {
return new Promise(resolve => {
scheduleNextTick(resolve);
});
}

// Testing async behavior
async function testNextTickBehavior() {
const events: string[] = [];

events.push('test-start');

scheduleNextTick(() => {
events.push('next-tick-1');
});

scheduleNextTick(() => {
events.push('next-tick-2');
});

// Wait for next tick operations to complete
await flushNextTick();
await flushNextTick(); // Ensure nested operations complete

console.log(events);
// ['test-start', 'next-tick-1', 'next-tick-2']
}

// Development debugging helper
function debugEventLoop(label: string) {
console.log(`${label}: sync`);

queueMicrotask(() => {
console.log(`${label}: microtask`);
});

scheduleNextTick(() => {
console.log(`${label}: next-tick`);
});

setTimeout(() => {
console.log(`${label}: timeout`);
}, 0);
}

Performance optimization patterns

// Batch operations for next tick processing
class NextTickBatchProcessor {
private pendingOperations: (() => void)[] = [];
private processing = false;

addOperation(operation: () => void) {
this.pendingOperations.push(operation);
this.scheduleBatch();
}

private scheduleBatch() {
if (!this.processing) {
this.processing = true;

scheduleNextTick(() => {
this.processBatch();
this.processing = false;
});
}
}

private processBatch() {
const operations = [...this.pendingOperations];
this.pendingOperations.length = 0;

operations.forEach(operation => {
try {
operation();
} catch (error) {
console.error('Batch operation error:', error);
}
});
}
}

// I/O intensive operation scheduling
function scheduleIOIntensiveWork(work: () => void) {
// Defer I/O work to next tick to avoid blocking current operations
scheduleNextTick(() => {
const startTime = Date.now();
work();
const duration = Date.now() - startTime;

if (duration > 10) {
console.warn(`Long-running operation: ${duration}ms`);
}
});
}

Cross-platform compatibility patterns

// Universal next tick scheduler with fallback
function createUniversalNextTick() {
// Get the optimal scheduler for current environment
const scheduler = scheduleNextTick;

return {
schedule: (task: () => void) => scheduler(task),

// Promise-based variant
promise: (): Promise<void> => {
return new Promise(resolve => {
scheduler(resolve);
});
},

// Multiple task scheduling
scheduleAll: (tasks: (() => void)[]) => {
scheduler(() => {
tasks.forEach(task => {
try {
task();
} catch (error) {
console.error('Scheduled task error:', error);
}
});
});
}
};
}

// Environment-aware scheduling
function getEnvironmentInfo() {
const hasProcessNextTick = typeof process?.nextTick === 'function';
const hasSetImmediate = typeof setImmediate === 'function';

return {
environment: hasProcessNextTick ? 'node' : hasSetImmediate ? 'browser-advanced' : 'browser-basic',
scheduler: hasProcessNextTick ? 'process.nextTick' : hasSetImmediate ? 'setImmediate' : 'setTimeout',
supportsNextTick: true // Always supported through this utility
};
}

Playground

import { scheduleNextTick } from '@winglet/common-utils';

console.log('1: Synchronous code');

// Schedule for next tick (after microtasks and current I/O)
scheduleNextTick(() => {
console.log('5: Next tick executed');
});

// Microtask (executes before next tick)
queueMicrotask(() => {
console.log('3: Microtask executed');
});

// Promise (also microtask)
Promise.resolve().then(() => {
console.log('4: Promise resolved');
});

console.log('2: More synchronous code');

// Output order:
// 1: Synchronous code
// 2: More synchronous code
// 3: Microtask executed
// 4: Promise resolved
// 5: Next tick executed

Notes

Event Loop Integration:

  • Node.js: Executes after current I/O phase, before next I/O polling cycle
  • Browsers: Executes after current macrotask, similar to setImmediate timing
  • Universal: Consistent "next tick" semantics across all platforms
  • Priority: Lower than microtasks, higher than regular macrotasks

Platform-Specific Behavior:

  • Node.js: Uses Promise.resolve().then(() => process.nextTick(task)) for I/O coordination
  • Modern Browsers: Uses setImmediate for optimal next-tick semantics where available
  • Legacy Browsers: Falls back to setTimeout(0) with inherent timing constraints
  • Web Workers: Consistent behavior using available scheduling mechanisms

Performance Characteristics:

  • Time Complexity: O(1) for scheduling operation
  • Space Complexity: O(1) per scheduled task
  • Node.js: ~0.001ms scheduling overhead with precise I/O timing
  • Browser: ~0.01ms with setImmediate, ~1-4ms with setTimeout fallback

Comparison with Alternatives:

  • process.nextTick (Node.js): Direct access, but Node.js specific
  • setImmediate: Similar timing, but limited browser support
  • setTimeout(0): Universal but subject to minimum delays
  • queueMicrotask: Executes earlier in different event loop phase
  • scheduleMacrotask: Similar timing but different implementation priorities

Use Cases:

  • Server-side I/O operation coordination
  • Resource cleanup and lifecycle management
  • Database operation sequencing
  • Event loop phase coordination
  • Testing and debugging async behavior
  • Performance optimization through deferred execution
  • Cross-platform next-tick semantics
  • Application shutdown and cleanup procedures

Best Practices:

  • Use for operations that should run after current I/O phase
  • Prefer for server-side applications requiring I/O coordination
  • Ideal for cleanup operations and resource management
  • Combine with error handling for robust task execution
  • Use in testing utilities to control async execution flow
  • Consider for cross-platform applications requiring consistent timing

Node.js Specific Advantages:

  • I/O Coordination: Optimal timing relative to file system and network operations
  • Process Integration: Leverages Node.js event loop characteristics
  • Server Performance: Optimized for server-side application patterns
  • Memory Efficiency: Minimal overhead with native process.nextTick

Browser Compatibility:

  • Modern Browsers: Uses setImmediate where available for optimal timing
  • Legacy Support: Graceful fallback to setTimeout for universal compatibility
  • Worker Contexts: Consistent behavior in Web Workers and Service Workers
  • Electron: Follows appropriate behavior based on main/renderer process context