Time-based Operators and Temporal Logic
1. debounceTime for Input Stabilization
| Feature | Syntax | Description | Timing |
|---|---|---|---|
| debounceTime | debounceTime(dueTime, scheduler?) |
Emits value only after dueTime silence period (no new emissions) | Trailing edge - emits after quiet period |
| Reset on emission | Each new emission resets the timer | Only emits when source stops emitting | Waits for stabilization |
| Use case | Search input, resize events, auto-save | Wait for user to stop typing/acting | Reduce API calls |
Example: debounceTime for input stabilization
import { fromEvent, interval } from 'rxjs';
import { debounceTime, map, distinctUntilChanged } from 'rxjs/operators';
// Basic debounceTime - wait for quiet period
const input$ = fromEvent(searchInput, 'input');
input$.pipe(
map(e => e.target.value),
debounceTime(300), // Wait 300ms after typing stops
distinctUntilChanged()
).subscribe(searchTerm => {
console.log('Search for:', searchTerm);
performSearch(searchTerm);
});
// Rapid emissions - only last emitted
interval(100).pipe(
take(10),
debounceTime(500)
).subscribe(val => console.log('Debounced:', val));
// Only emits value 9 (after 500ms silence)
// Practical: Type-ahead search
fromEvent(searchBox, 'input').pipe(
map(e => e.target.value),
debounceTime(400), // Wait 400ms after user stops typing
distinctUntilChanged(),
filter(term => term.length >= 2),
switchMap(term => ajax.getJSON(`/api/search?q=${term}`))
).subscribe(results => {
displaySearchResults(results);
// Reduces API calls dramatically
});
// Practical: Auto-save form
const formChanges$ = merge(
fromEvent(input1, 'input'),
fromEvent(input2, 'input'),
fromEvent(textarea, 'input')
);
formChanges$.pipe(
debounceTime(2000), // Save 2s after user stops editing
map(() => getFormData()),
distinctUntilChanged((prev, curr) =>
JSON.stringify(prev) === JSON.stringify(curr)
),
switchMap(data => ajax.post('/api/save', data))
).subscribe({
next: () => showSavedIndicator(),
error: err => showSaveError(err)
});
// Practical: Window resize handling
fromEvent(window, 'resize').pipe(
debounceTime(250), // Wait 250ms after resize stops
map(() => ({
width: window.innerWidth,
height: window.innerHeight
}))
).subscribe(dimensions => {
recalculateLayout(dimensions);
repositionElements(dimensions);
// Prevents excessive layout calculations
});
// Practical: Scroll end detection
fromEvent(window, 'scroll').pipe(
debounceTime(150), // User stopped scrolling
map(() => window.scrollY)
).subscribe(scrollPosition => {
saveScrollPosition(scrollPosition);
checkIfAtBottom(scrollPosition);
});
// Practical: Live validation with delay
fromEvent(emailInput, 'input').pipe(
map(e => e.target.value),
debounceTime(600),
distinctUntilChanged(),
switchMap(email => validateEmail(email))
).subscribe(validation => {
if (validation.valid) {
showValidIcon();
} else {
showInvalidIcon(validation.errors);
}
});
// Practical: Infinite scroll trigger
fromEvent(window, 'scroll').pipe(
debounceTime(100),
filter(() => isNearBottom()),
switchMap(() => loadMoreItems())
).subscribe(items => appendItems(items));
// Practical: Text editor auto-complete
fromEvent(editor, 'input').pipe(
map(e => ({
text: e.target.value,
cursorPosition: e.target.selectionStart
})),
debounceTime(300),
filter(state => shouldShowAutoComplete(state)),
switchMap(state => getSuggestions(state.text))
).subscribe(suggestions => displayAutoComplete(suggestions));
// Practical: Real-time collaboration presence
const userActivity$ = merge(
fromEvent(document, 'mousemove'),
fromEvent(document, 'keypress'),
fromEvent(document, 'click')
);
userActivity$.pipe(
debounceTime(5000), // 5s of inactivity
mapTo('idle')
).subscribe(status => {
updatePresenceStatus(status);
sendStatusToServer(status);
});
// Practical: Filter panel updates
const filterChanges$ = merge(
fromEvent(priceRange, 'input'),
fromEvent(categorySelect, 'change'),
fromEvent(ratingFilter, 'change')
);
filterChanges$.pipe(
debounceTime(500),
map(() => getFilterValues()),
distinctUntilChanged((a, b) => deepEqual(a, b)),
switchMap(filters => ajax.post('/api/filter', filters))
).subscribe(results => updateProductList(results));
// Practical: Chat typing indicator
fromEvent(chatInput, 'input').pipe(
debounceTime(1000),
mapTo(false) // Stopped typing
).subscribe(isTyping => {
sendTypingStatus(isTyping);
});
// With immediate emission on first input
fromEvent(chatInput, 'input').pipe(
tap(() => sendTypingStatus(true)), // Immediate
debounceTime(1000),
tap(() => sendTypingStatus(false)) // After stop
).subscribe();
// Comparison: debounce vs throttle
// debounceTime: Waits for silence, emits last
fromEvent(button, 'click').pipe(
debounceTime(1000)
).subscribe(() => console.log('Debounced click'));
// throttleTime: Emits first, ignores until period ends
fromEvent(button, 'click').pipe(
throttleTime(1000)
).subscribe(() => console.log('Throttled click'));
Note: debounceTime emits after silence period - perfect for
waiting until user finishes action. Drastically reduces API calls for search/autocomplete.
2. throttleTime for Rate Limiting
| Feature | Syntax | Description | Timing |
|---|---|---|---|
| throttleTime | throttleTime(duration, scheduler?, config?) |
Emits first value, then ignores for duration | Leading edge by default |
| Configuration | { leading: true, trailing: false } |
Control leading/trailing edge behavior | Flexible timing control |
| Use case | Button clicks, scroll events, mouse moves | Limit event frequency | Performance optimization |
Example: throttleTime for rate limiting
import { fromEvent, interval } from 'rxjs';
import { throttleTime, map, scan } from 'rxjs/operators';
// Basic throttleTime - emit first, ignore rest
fromEvent(button, 'click').pipe(
throttleTime(1000)
).subscribe(() => console.log('Throttled click'));
// First click processed, others ignored for 1s
// Rapid emissions throttled
interval(100).pipe(
take(20),
throttleTime(1000)
).subscribe(val => console.log('Throttled:', val));
// Emits: 0, 10 (approx)
// Trailing edge configuration
fromEvent(button, 'click').pipe(
throttleTime(1000, asyncScheduler, {
leading: false,
trailing: true
})
).subscribe(() => console.log('Trailing throttle'));
// Practical: Scroll event throttling
fromEvent(window, 'scroll').pipe(
throttleTime(200), // Max 5 times per second
map(() => window.scrollY)
).subscribe(scrollY => {
updateScrollProgress(scrollY);
toggleBackToTop(scrollY);
// Prevents excessive calculations
});
// Practical: Button click protection
fromEvent(submitButton, 'click').pipe(
throttleTime(2000) // Prevent double-click
).subscribe(() => {
console.log('Processing submission...');
submitForm();
});
// Practical: Mouse move tracking (performance)
fromEvent(canvas, 'mousemove').pipe(
throttleTime(50), // ~20 updates per second
map(e => ({ x: e.clientX, y: e.clientY }))
).subscribe(position => {
updateCursor(position);
drawAtPosition(position);
});
// Practical: Window resize (immediate response)
fromEvent(window, 'resize').pipe(
throttleTime(100) // Leading edge - immediate first response
).subscribe(() => {
updateResponsiveLayout();
// First resize handled immediately, then throttled
});
// Practical: API rate limiting
const apiCalls$ = new Subject<Request>();
apiCalls$.pipe(
throttleTime(1000), // Max 1 call per second
switchMap(request => ajax(request))
).subscribe(response => handleResponse(response));
// Queue multiple calls
apiCalls$.next(request1);
apiCalls$.next(request2); // Ignored
apiCalls$.next(request3); // Ignored
// Only request1 processed
// Practical: Save button rate limiting
fromEvent(saveBtn, 'click').pipe(
throttleTime(3000),
switchMap(() => saveData())
).subscribe({
next: () => showSaveSuccess(),
error: err => showSaveError(err)
});
// Practical: Infinite scroll throttling
fromEvent(window, 'scroll').pipe(
throttleTime(300),
filter(() => isNearBottom()),
exhaustMap(() => loadNextPage())
).subscribe(items => appendItems(items));
// Practical: Keyboard event throttling
fromEvent(document, 'keydown').pipe(
throttleTime(100),
map(e => e.key)
).subscribe(key => {
handleKeyPress(key);
// Prevents key repeat spam
});
// Practical: Analytics event throttling
const trackEvent = (category: string, action: string) => {
return of({ category, action }).pipe(
throttleTime(5000) // Max once per 5 seconds
).subscribe(event => {
analytics.track(event.category, event.action);
});
};
// Practical: Drag and drop throttling
fromEvent(element, 'drag').pipe(
throttleTime(16), // ~60fps
map(e => ({ x: e.clientX, y: e.clientY }))
).subscribe(position => {
updateDragPreview(position);
});
// Both leading and trailing
fromEvent(input, 'input').pipe(
throttleTime(1000, asyncScheduler, {
leading: true, // Immediate first
trailing: true // Last after period
}),
map(e => e.target.value)
).subscribe(value => processValue(value));
// Practical: Network request throttling
const requests$ = new Subject<string>();
requests$.pipe(
throttleTime(200),
switchMap(url => ajax.getJSON(url))
).subscribe(data => updateUI(data));
// Practical: Game input throttling
fromEvent(document, 'keypress').pipe(
filter(e => e.key === ' '), // Spacebar
throttleTime(500) // Limit firing rate
).subscribe(() => {
fireWeapon();
});
// Practical: Audio visualization throttling
audioData$.pipe(
throttleTime(33) // ~30fps for visualization
).subscribe(data => {
updateVisualization(data);
});
Note: throttleTime emits immediately (leading edge), then
ignores emissions for duration. Use for performance optimization of high-frequency events.
3. auditTime for Trailing Edge Sampling
| Feature | Syntax | Description | Edge |
|---|---|---|---|
| auditTime | auditTime(duration, scheduler?) |
Ignores values for duration, then emits most recent | Trailing edge |
| vs throttleTime | audit = trailing, throttle = leading | Opposite timing behavior | Different use cases |
| Use case | Final state after activity, settling values | Capture result after action completes | Post-action processing |
Example: auditTime for trailing edge sampling
import { fromEvent, interval } from 'rxjs';
import { auditTime, map } from 'rxjs/operators';
// Basic auditTime - emit last value after period
fromEvent(button, 'click').pipe(
auditTime(1000)
).subscribe(() => console.log('Audit: last click after 1s'));
// Rapid emissions - emits last
interval(100).pipe(
take(15),
auditTime(1000)
).subscribe(val => console.log('Audited:', val));
// Emits values at trailing edge
// Practical: Capture final scroll position
fromEvent(window, 'scroll').pipe(
auditTime(200),
map(() => window.scrollY)
).subscribe(finalScrollY => {
saveScrollPosition(finalScrollY);
updateSectionHighlight(finalScrollY);
// Uses final position after scroll settles
});
// Practical: Final resize dimensions
fromEvent(window, 'resize').pipe(
auditTime(300),
map(() => ({
width: window.innerWidth,
height: window.innerHeight
}))
).subscribe(finalDimensions => {
recalculateLayout(finalDimensions);
// Uses final window size
});
// Practical: Input field final value
fromEvent(input, 'input').pipe(
map(e => e.target.value),
auditTime(500)
).subscribe(finalValue => {
console.log('Final value:', finalValue);
performValidation(finalValue);
});
// Practical: Mouse position after movement stops
fromEvent(canvas, 'mousemove').pipe(
auditTime(200),
map(e => ({ x: e.clientX, y: e.clientY }))
).subscribe(finalPosition => {
showContextMenuAt(finalPosition);
// Shows menu at final mouse position
});
// Practical: Slider final value
fromEvent(slider, 'input').pipe(
map(e => e.target.value),
auditTime(300)
).subscribe(finalValue => {
applyFilterWithValue(finalValue);
// Applies filter after user stops adjusting
});
// Practical: Final drag position
fromEvent(draggable, 'drag').pipe(
auditTime(100),
map(e => ({ x: e.clientX, y: e.clientY }))
).subscribe(finalPosition => {
snapToGrid(finalPosition);
savePosition(finalPosition);
});
// Compare timing operators
const clicks$ = fromEvent(button, 'click');
// throttleTime: First click immediately
clicks$.pipe(throttleTime(1000))
.subscribe(() => console.log('Throttle: first'));
// auditTime: Last click after period
clicks$.pipe(auditTime(1000))
.subscribe(() => console.log('Audit: last'));
// debounceTime: After clicking stops
clicks$.pipe(debounceTime(1000))
.subscribe(() => console.log('Debounce: after stop'));
// Practical: Color picker final selection
fromEvent(colorPicker, 'input').pipe(
map(e => e.target.value),
auditTime(400)
).subscribe(finalColor => {
applyColor(finalColor);
addToRecentColors(finalColor);
});
// Practical: Volume slider final value
fromEvent(volumeSlider, 'input').pipe(
map(e => e.target.value),
auditTime(200)
).subscribe(finalVolume => {
setVolume(finalVolume);
saveVolumePreference(finalVolume);
});
// Practical: Zoom level final value
fromEvent(canvas, 'wheel').pipe(
map(e => e.deltaY),
scan((zoom, delta) => {
const newZoom = zoom + (delta > 0 ? -0.1 : 0.1);
return Math.max(0.1, Math.min(5, newZoom));
}, 1),
auditTime(150)
).subscribe(finalZoom => {
applyZoom(finalZoom);
renderAtZoomLevel(finalZoom);
});
4. sampleTime for Periodic Value Sampling
| Feature | Syntax | Description | Sampling |
|---|---|---|---|
| sampleTime | sampleTime(period, scheduler?) |
Emits most recent value at periodic intervals | Fixed time intervals |
| Behavior | Only emits if source emitted since last sample | Silent periods produce no emissions | Conditional periodic |
| Use case | Periodic snapshots, data sampling, UI updates | Regular polling of changing values | Time-based reduction |
Example: sampleTime for periodic sampling
import { fromEvent, interval } from 'rxjs';
import { sampleTime, map, scan } from 'rxjs/operators';
// Basic sampleTime - periodic sampling
interval(100).pipe(
sampleTime(1000)
).subscribe(val => console.log('Sampled:', val));
// Samples every 1 second
// Practical: Mouse position sampling
fromEvent(document, 'mousemove').pipe(
map(e => ({ x: e.clientX, y: e.clientY })),
sampleTime(500) // Sample every 500ms
).subscribe(position => {
logMousePosition(position);
// Reduces logging from potentially 1000s to 2/second
});
// Practical: Real-time metrics sampling
const cpuUsage$ = interval(50).pipe(
map(() => getCPUUsage())
);
cpuUsage$.pipe(
sampleTime(5000) // Sample every 5 seconds
).subscribe(usage => {
displayMetric('CPU', usage);
sendToMonitoring(usage);
});
// Practical: Sensor data sampling
const temperatureSensor$ = interval(100).pipe(
map(() => readTemperature())
);
temperatureSensor$.pipe(
sampleTime(10000) // Sample every 10 seconds
).subscribe(temp => {
logTemperature(temp);
checkThresholds(temp);
});
// Practical: Network bandwidth monitoring
const bytesTransferred$ = new Subject<number>();
bytesTransferred$.pipe(
scan((total, bytes) => total + bytes, 0),
sampleTime(1000) // Sample every second
).subscribe(totalBytes => {
const bandwidth = calculateBandwidth(totalBytes);
updateBandwidthDisplay(bandwidth);
});
// Practical: Stock price ticker
const priceUpdates$ = websocket$.pipe(
filter(msg => msg.type === 'price'),
map(msg => msg.price)
);
priceUpdates$.pipe(
sampleTime(1000) // Update display every second
).subscribe(price => {
updatePriceTicker(price);
// Prevents excessive UI updates
});
// Practical: Audio level meter
const audioLevel$ = new Subject<number>();
audioLevel$.pipe(
sampleTime(100) // Sample 10 times per second
).subscribe(level => {
updateAudioMeter(level);
checkForClipping(level);
});
// Practical: GPS location sampling
const gpsUpdates$ = new Subject<Location>();
gpsUpdates$.pipe(
sampleTime(30000) // Sample every 30 seconds
).subscribe(location => {
saveLocationHistory(location);
updateMapPosition(location);
});
// Practical: Form field activity monitoring
const formActivity$ = merge(
fromEvent(input1, 'input'),
fromEvent(input2, 'input'),
fromEvent(select1, 'change')
).pipe(
mapTo(Date.now())
);
formActivity$.pipe(
sampleTime(60000) // Check every minute
).subscribe(lastActivity => {
updateSessionTimeout(lastActivity);
});
// Practical: Chart data point collection
const dataStream$ = new Subject<number>();
dataStream$.pipe(
sampleTime(2000) // Add point every 2 seconds
).subscribe(value => {
addDataPointToChart(value);
// Prevents chart from becoming too dense
});
// Practical: Scroll progress indicator
fromEvent(window, 'scroll').pipe(
map(() => {
const scrolled = window.scrollY;
const height = document.documentElement.scrollHeight - window.innerHeight;
return (scrolled / height) * 100;
}),
sampleTime(200)
).subscribe(progress => {
updateProgressBar(progress);
});
// Practical: Performance monitoring
const performanceMetrics$ = interval(100).pipe(
map(() => ({
fps: measureFPS(),
memory: performance.memory?.usedJSHeapSize,
timestamp: Date.now()
}))
);
performanceMetrics$.pipe(
sampleTime(5000)
).subscribe(metrics => {
logPerformanceMetrics(metrics);
});
5. timeInterval for Emission Time Measurement
| Feature | Syntax | Description | Output |
|---|---|---|---|
| timeInterval | timeInterval(scheduler?) |
Wraps each emission with time elapsed since previous emission | { value, interval } |
| Interval measurement | Time in milliseconds between emissions | Measures spacing between values | Temporal analytics |
| Use case | Performance monitoring, rate detection, timing analysis | Understanding emission patterns | Debugging and metrics |
Example: timeInterval for timing measurement
import { fromEvent, interval } from 'rxjs';
import { timeInterval, map, tap, take } from 'rxjs/operators';
// Basic timeInterval - measure spacing
interval(1000).pipe(
take(5),
timeInterval()
).subscribe(({ value, interval }) => {
console.log(`Value: ${value}, Interval: ${interval}ms`);
});
// Output shows ~1000ms intervals
// Practical: Click rate detection
fromEvent(button, 'click').pipe(
timeInterval(),
tap(({ interval }) => {
if (interval < 200) {
console.log('Rapid clicking detected!');
}
})
).subscribe(({ value, interval }) => {
console.log(`Clicked (${interval}ms since last)`);
});
// Practical: API response time monitoring
ajax.getJSON('/api/data').pipe(
timeInterval()
).subscribe(({ value, interval }) => {
console.log(`Response time: ${interval}ms`);
metrics.recordResponseTime(interval);
if (interval > 2000) {
console.warn('Slow API response');
}
});
// Practical: User typing speed analysis
fromEvent(input, 'input').pipe(
timeInterval(),
map(({ interval }) => interval),
scan((acc, interval) => {
acc.intervals.push(interval);
acc.average = acc.intervals.reduce((a, b) => a + b) / acc.intervals.length;
return acc;
}, { intervals: [], average: 0 })
).subscribe(stats => {
console.log(`Avg typing interval: ${stats.average.toFixed(0)}ms`);
displayTypingSpeed(stats);
});
// Practical: Network event spacing
const networkEvents$ = merge(
fromEvent(connection, 'message').pipe(mapTo('message')),
fromEvent(connection, 'error').pipe(mapTo('error')),
fromEvent(connection, 'close').pipe(mapTo('close'))
);
networkEvents$.pipe(
timeInterval()
).subscribe(({ value, interval }) => {
console.log(`${value} after ${interval}ms`);
if (value === 'error' && interval < 1000) {
console.error('Rapid errors - connection unstable');
}
});
// Practical: Frame rate measurement
const frames$ = interval(0, animationFrameScheduler).pipe(
take(120)
);
frames$.pipe(
timeInterval(),
scan((acc, { interval }) => {
acc.frameTime = interval;
acc.fps = 1000 / interval;
return acc;
}, { frameTime: 0, fps: 0 })
).subscribe(stats => {
displayFPS(stats.fps);
if (stats.fps < 30) {
console.warn('Low frame rate detected');
}
});
// Practical: Message rate limiting detection
const messages$ = new Subject<Message>();
messages$.pipe(
timeInterval(),
filter(({ interval }) => interval < 100),
scan((count) => count + 1, 0)
).subscribe(rapidCount => {
if (rapidCount > 10) {
console.warn('Message spam detected');
throttleMessages();
}
});
// Practical: Keyboard event analysis
fromEvent(document, 'keydown').pipe(
timeInterval(),
map(({ value, interval }) => ({
key: value.key,
interval
}))
).subscribe(({ key, interval }) => {
console.log(`Key '${key}' pressed ${interval}ms after previous`);
if (interval < 50) {
console.log('Rapid key press (possible key repeat)');
}
});
// Practical: Stream health monitoring
dataStream$.pipe(
timeInterval(),
tap(({ interval }) => {
if (interval > 5000) {
console.warn('Stream stalled - no data for 5s');
checkStreamHealth();
}
})
).subscribe();
// Practical: Request pacing analysis
requestStream$.pipe(
timeInterval(),
scan((stats, { interval }) => {
stats.count++;
stats.totalTime += interval;
stats.avgInterval = stats.totalTime / stats.count;
stats.minInterval = Math.min(stats.minInterval, interval);
stats.maxInterval = Math.max(stats.maxInterval, interval);
return stats;
}, {
count: 0,
totalTime: 0,
avgInterval: 0,
minInterval: Infinity,
maxInterval: 0
})
).subscribe(stats => {
displayRequestStats(stats);
});
// Practical: Heartbeat monitoring
heartbeat$.pipe(
timeInterval(),
tap(({ interval }) => {
if (interval > 35000) { // Expected every 30s
console.error('Missed heartbeat');
attemptReconnect();
}
})
).subscribe();
Note: timeInterval wraps values with timing metadata. Useful for
performance monitoring, rate detection, and temporal pattern analysis.
6. timestamp for Emission Timestamping
| Feature | Syntax | Description | Output |
|---|---|---|---|
| timestamp | timestamp(scheduler?) |
Wraps each emission with absolute timestamp | { value, timestamp } |
| Timestamp format | Milliseconds since Unix epoch | Absolute time, not relative | Date.now() equivalent |
| Use case | Event logging, audit trails, temporal ordering | Record when events occurred | Historical tracking |
Example: timestamp for emission timestamping
import { fromEvent, interval } from 'rxjs';
import { timestamp, map, scan, take } from 'rxjs/operators';
// Basic timestamp - add absolute timestamp
interval(1000).pipe(
take(3),
timestamp()
).subscribe(({ value, timestamp }) => {
const date = new Date(timestamp);
console.log(`Value: ${value} at ${date.toISOString()}`);
});
// Practical: Click event logging
fromEvent(button, 'click').pipe(
timestamp(),
map(({ value, timestamp }) => ({
event: 'click',
element: value.target.id,
timestamp,
date: new Date(timestamp).toISOString()
}))
).subscribe(log => {
console.log('Click log:', log);
sendToAnalytics(log);
});
// Practical: Error logging with timestamps
errors$.pipe(
timestamp(),
tap(({ value, timestamp }) => {
const errorLog = {
error: value,
timestamp,
datetime: new Date(timestamp).toISOString(),
stackTrace: value.stack
};
logError(errorLog);
sendToErrorTracking(errorLog);
})
).subscribe();
// Practical: User activity timeline
const userActions$ = merge(
fromEvent(document, 'click').pipe(mapTo('click')),
fromEvent(document, 'keypress').pipe(mapTo('keypress')),
fromEvent(document, 'scroll').pipe(mapTo('scroll'))
);
userActions$.pipe(
timestamp(),
scan((timeline, { value, timestamp }) => {
timeline.push({
action: value,
timestamp,
time: new Date(timestamp).toLocaleTimeString()
});
return timeline.slice(-50); // Keep last 50 actions
}, [])
).subscribe(timeline => {
updateActivityTimeline(timeline);
});
// Practical: Message timestamps for chat
const messages$ = new Subject<string>();
messages$.pipe(
timestamp(),
map(({ value, timestamp }) => ({
text: value,
timestamp,
formattedTime: new Date(timestamp).toLocaleTimeString(),
sender: currentUser.id
}))
).subscribe(message => {
displayMessage(message);
saveToHistory(message);
});
// Practical: Performance event tracking
const performanceEvents$ = merge(
fromEvent(window, 'load').pipe(mapTo('page_load')),
ajaxComplete$.pipe(mapTo('ajax_complete')),
renderComplete$.pipe(mapTo('render_complete'))
);
performanceEvents$.pipe(
timestamp(),
scan((metrics, { value, timestamp }) => {
metrics[value] = timestamp;
if (metrics.page_load && metrics.render_complete) {
metrics.timeToInteractive =
metrics.render_complete - metrics.page_load;
}
return metrics;
}, {})
).subscribe(metrics => {
reportPerformanceMetrics(metrics);
});
// Practical: Sensor data logging
const sensorData$ = interval(1000).pipe(
map(() => ({
temperature: readTemperature(),
humidity: readHumidity(),
pressure: readPressure()
}))
);
sensorData$.pipe(
timestamp(),
map(({ value, timestamp }) => ({
...value,
timestamp,
datetime: new Date(timestamp).toISOString()
}))
).subscribe(reading => {
saveSensorReading(reading);
if (reading.temperature > 30) {
logAlert({
type: 'high_temp',
value: reading.temperature,
timestamp: reading.timestamp
});
}
});
// Practical: API request/response logging
ajax.getJSON('/api/data').pipe(
timestamp(),
tap(({ timestamp }) => {
console.log(`Request completed at: ${new Date(timestamp).toISOString()}`);
})
).subscribe();
// Practical: Form submission audit trail
fromEvent(form, 'submit').pipe(
timestamp(),
map(({ value, timestamp }) => ({
formData: new FormData(value.target),
submittedAt: timestamp,
submittedBy: currentUser.id,
formattedDate: new Date(timestamp).toISOString()
}))
).subscribe(submission => {
auditLog.record(submission);
processSubmission(submission);
});
// Practical: State change history
stateChanges$.pipe(
timestamp(),
scan((history, { value, timestamp }) => {
history.push({
state: value,
timestamp,
date: new Date(timestamp).toISOString()
});
return history.slice(-100); // Keep last 100 changes
}, [])
).subscribe(history => {
saveStateHistory(history);
enableUndoRedo(history);
});
// Practical: WebSocket message timestamps
websocket$.pipe(
timestamp(),
map(({ value, timestamp }) => ({
...value,
receivedAt: timestamp,
latency: timestamp - value.sentAt
}))
).subscribe(message => {
console.log(`Message latency: ${message.latency}ms`);
processMessage(message);
});
// Practical: Download progress timestamps
downloadProgress$.pipe(
timestamp(),
map(({ value, timestamp }) => ({
bytesDownloaded: value,
timestamp,
elapsedTime: timestamp - downloadStartTime,
speed: value / ((timestamp - downloadStartTime) / 1000) // bytes/sec
}))
).subscribe(progress => {
updateProgressBar(progress);
displayDownloadSpeed(progress.speed);
});
// Compare timeInterval vs timestamp
const source$ = interval(1000).pipe(take(3));
// timeInterval: relative time between emissions
source$.pipe(timeInterval())
.subscribe(x => console.log('Interval:', x));
// { value: 0, interval: 1000 }, { value: 1, interval: 1000 }
// timestamp: absolute time of emission
source$.pipe(timestamp())
.subscribe(x => console.log('Timestamp:', x));
// { value: 0, timestamp: 1703347200000 }, ...
Note: timestamp adds absolute time (Date.now()), while timeInterval measures relative time between emissions.
Section 12 Summary
- debounceTime waits for silence period before emitting - perfect for search/autocomplete
- throttleTime emits first value immediately, ignores rest for duration - rate limiting
- auditTime emits last value after period (trailing edge) - final state capture
- sampleTime periodically emits most recent value - regular snapshots
- timeInterval measures time between emissions - performance monitoring
- timestamp adds absolute timestamp to emissions - event logging and audit trails