📚 4 min read
Custom setInterval Examples ​
Learn how to create and use custom setInterval implementations with advanced features.
Basic Usage ​
typescript
// Promise-based interval
function setIntervalAsync(
callback: () => Promise<void>,
ms: number
): () => void {
let active = true;
(async () => {
while (active) {
await callback();
await new Promise((resolve) => setTimeout(resolve, ms));
}
})();
return () => {
active = false;
};
}
// Cancellable interval
function setIntervalCancellable(callback: () => void, ms: number): () => void {
const intervalId = setInterval(callback, ms);
return () => clearInterval(intervalId);
}
// Interval with max executions
function setIntervalWithLimit(
callback: () => void,
ms: number,
maxExecutions: number
): () => void {
let count = 0;
const intervalId = setInterval(() => {
callback();
count++;
if (count >= maxExecutions) {
clearInterval(intervalId);
}
}, ms);
return () => clearInterval(intervalId);
}
Advanced Patterns ​
Dynamic Interval ​
typescript
class DynamicInterval {
private intervalId: NodeJS.Timeout | null = null;
private currentDelay: number;
private lastExecutionTime: number = 0;
constructor(
private callback: () => void,
private getNextDelay: () => number,
initialDelay: number
) {
this.currentDelay = initialDelay;
}
start(): void {
if (this.intervalId) return;
this.scheduleNext();
}
private scheduleNext(): void {
this.intervalId = setTimeout(() => {
const now = Date.now();
this.callback();
this.lastExecutionTime = now;
this.currentDelay = this.getNextDelay();
this.scheduleNext();
}, this.currentDelay);
}
stop(): void {
if (this.intervalId) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
}
getTimeSinceLastExecution(): number {
return this.lastExecutionTime ? Date.now() - this.lastExecutionTime : 0;
}
getCurrentDelay(): number {
return this.currentDelay;
}
}
// Usage example
const interval = new DynamicInterval(
() => console.log('Tick'),
() => Math.random() * 1000 + 500, // Random delay between 500-1500ms
1000
);
Precise Interval ​
typescript
class PreciseInterval {
private intervalId: NodeJS.Timeout | null = null;
private startTime: number = 0;
private executionCount: number = 0;
constructor(
private callback: () => void,
private delay: number
) {}
start(): void {
if (this.intervalId) return;
this.startTime = Date.now();
this.executionCount = 0;
this.scheduleNext();
}
private scheduleNext(): void {
const nextExecutionTime =
this.startTime + (this.executionCount + 1) * this.delay;
const now = Date.now();
const adjustment = nextExecutionTime - now;
this.intervalId = setTimeout(() => {
this.callback();
this.executionCount++;
this.scheduleNext();
}, adjustment);
}
stop(): void {
if (this.intervalId) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
}
getDrift(): number {
if (!this.startTime) return 0;
const expectedTime = this.startTime + this.executionCount * this.delay;
return Date.now() - expectedTime;
}
}
Throttled Interval ​
typescript
interface ThrottleOptions {
minDelay: number;
maxDelay: number;
targetLoad: number;
}
class ThrottledInterval {
private intervalId: NodeJS.Timeout | null = null;
private currentDelay: number;
private loadHistory: number[] = [];
private readonly historySize = 10;
constructor(
private callback: () => Promise<void>,
private options: ThrottleOptions
) {
this.currentDelay = options.minDelay;
}
start(): void {
if (this.intervalId) return;
this.scheduleNext();
}
private async scheduleNext(): Promise<void> {
this.intervalId = setTimeout(async () => {
const startTime = Date.now();
try {
await this.callback();
} catch (error) {
console.error('Execution error:', error);
}
const executionTime = Date.now() - startTime;
this.updateDelay(executionTime);
this.scheduleNext();
}, this.currentDelay);
}
private updateDelay(executionTime: number): void {
// Calculate load as ratio of execution time to total time
const load = executionTime / (executionTime + this.currentDelay);
this.loadHistory.push(load);
if (this.loadHistory.length > this.historySize) {
this.loadHistory.shift();
}
// Calculate average load
const avgLoad =
this.loadHistory.reduce((a, b) => a + b) / this.loadHistory.length;
// Adjust delay based on load
if (avgLoad > this.options.targetLoad) {
// Increase delay if load is too high
this.currentDelay = Math.min(
this.options.maxDelay,
this.currentDelay * 1.2
);
} else if (avgLoad < this.options.targetLoad * 0.8) {
// Decrease delay if load is too low
this.currentDelay = Math.max(
this.options.minDelay,
this.currentDelay * 0.8
);
}
}
stop(): void {
if (this.intervalId) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
}
getCurrentDelay(): number {
return this.currentDelay;
}
getAverageLoad(): number {
if (this.loadHistory.length === 0) return 0;
return this.loadHistory.reduce((a, b) => a + b) / this.loadHistory.length;
}
}
Batch Interval ​
typescript
interface BatchIntervalOptions<T> {
delay: number;
maxBatchSize: number;
onBatch: (items: T[]) => Promise<void>;
}
class BatchInterval<T> {
private intervalId: NodeJS.Timeout | null = null;
private batch: T[] = [];
private processing = false;
constructor(private options: BatchIntervalOptions<T>) {}
start(): void {
if (this.intervalId) return;
this.intervalId = setInterval(async () => {
await this.processBatch();
}, this.options.delay);
}
async add(item: T): Promise<void> {
this.batch.push(item);
if (this.batch.length >= this.options.maxBatchSize) {
await this.processBatch();
}
}
private async processBatch(): Promise<void> {
if (this.processing || this.batch.length === 0) return;
this.processing = true;
const items = this.batch.splice(0, this.options.maxBatchSize);
try {
await this.options.onBatch(items);
} catch (error) {
console.error('Batch processing error:', error);
// Re-add items to the batch
this.batch.unshift(...items);
} finally {
this.processing = false;
}
}
stop(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
async flush(): Promise<void> {
await this.processBatch();
}
getBatchSize(): number {
return this.batch.length;
}
isProcessing(): boolean {
return this.processing;
}
}