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
| Name | Type | Description |
|---|---|---|
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
setImmediatefor 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