Skip to content

audit - Emit Last Value on Custom Trigger

The audit operator waits for a custom Observable to emit a value and emits the last value from the source during that period. While auditTime controls with a fixed time, audit can control the period dynamically with an Observable.

🔰 Basic Syntax and Usage

ts
import { fromEvent, interval } from 'rxjs';
import { audit } from 'rxjs';

// Click event
const clicks$ = fromEvent(document, 'click');

// Separate period every second
clicks$.pipe(
  audit(() => interval(1000))
).subscribe(() => {
  console.log('Click was recorded');
});
  • When a click occurs, a 1-second period begins.
  • Only the last click during that 1 second is emitted.
  • The next period starts after 1 second.

🌐 RxJS Official Documentation - audit

💡 Typical Usage Patterns

  • Sampling at dynamic intervals: Adjust period according to load
  • Custom timing control: Period control based on other Observables
  • Adaptive event limiting: Thinning according to circumstances

🔍 Difference from auditTime

OperatorPeriod ControlUse Case
auditTimeFixed time (milliseconds)Simple time-based control
auditCustom ObservableDynamic period control
ts
import { fromEvent, timer } from 'rxjs';
import { audit, auditTime } from 'rxjs';

const clicks$ = fromEvent(document, 'click');

// auditTime - Fixed 1 second
clicks$.pipe(
  auditTime(1000)
).subscribe(() => console.log('Fixed 1 second'));

// audit - Dynamic period
let period = 1000;
clicks$.pipe(
  audit(() => {
    period = Math.random() * 2000; // Random period 0-2 seconds
    return timer(period);
  })
).subscribe(() => console.log(`Dynamic period: ${period}ms`));

🧠 Practical Code Example: Dynamic Sampling According to Load

Example of adjusting sampling interval according to system load.

ts
import { fromEvent, timer } from 'rxjs';
import { audit, map } from 'rxjs';

// Create UI
const output = document.createElement('div');
output.innerHTML = '<h3>Dynamic Sampling</h3>';
document.body.appendChild(output);

const button = document.createElement('button');
button.textContent = 'Change Load';
document.body.appendChild(button);

const statusDiv = document.createElement('div');
statusDiv.style.marginTop = '10px';
output.appendChild(statusDiv);

const logDiv = document.createElement('div');
logDiv.style.marginTop = '10px';
logDiv.style.maxHeight = '200px';
logDiv.style.overflow = 'auto';
output.appendChild(logDiv);

// Load level (0: low, 1: medium, 2: high)
let loadLevel = 0;

fromEvent(button, 'click').subscribe(() => {
  loadLevel = (loadLevel + 1) % 3;
  const levels = ['Low Load', 'Medium Load', 'High Load'];
  statusDiv.textContent = `Current load: ${levels[loadLevel]}`;
});

// Mouse move event
const moves$ = fromEvent<MouseEvent>(document, 'mousemove');

moves$.pipe(
  audit(() => {
    // Adjust period according to load
    const periods = [2000, 1000, 500]; // Low load → long period, high load → short period
    return timer(periods[loadLevel]);
  }),
  map(event => ({ x: event.clientX, y: event.clientY }))
).subscribe(pos => {
  const log = document.createElement('div');
  log.textContent = `[${new Date().toLocaleTimeString()}] Mouse position: (${pos.x}, ${pos.y})`;
  logDiv.insertBefore(log, logDiv.firstChild);

  // Display maximum 10 items
  while (logDiv.children.length > 10) {
    logDiv.removeChild(logDiv.lastChild!);
  }
});
  • When load is low, thin out at 2-second intervals (power saving mode)
  • When load is high, sample finely at 500ms intervals
  • Period can be adjusted dynamically according to load

⚠️ Notes

1. First Value is Not Emitted Immediately

audit waits until the period ends after receiving the first value.

ts
import { interval, timer } from 'rxjs';
import { audit, take } from 'rxjs';

interval(100).pipe(
  audit(() => timer(1000)),
  take(3)
).subscribe(val => {
  console.log(val);
});
// Output:
// 9  (after 1 second, last value of 0-9)
// 19 (after 2 seconds, last value of 10-19)
// 29 (after 3 seconds, last value of 20-29)

2. Duration Observable Must Be Newly Generated Each Time

The function passed to audit must return a new Observable each time.

ts
// ❌ Bad example: Reuse same Observable instance
const duration$ = timer(1000);
source$.pipe(
  audit(() => duration$) // Doesn't work after 2nd time
).subscribe();

// ✅ Good example: Generate new Observable each time
source$.pipe(
  audit(() => timer(1000))
).subscribe();

🆚 Comparison with Similar Operators

OperatorEmission TimingEmitted ValueUse Case
auditAt period endLast value within periodGet latest state within period
throttleAt period startFirst value within periodGet first of consecutive events
debounceAfter silenceValue just before silenceWait for input completion
sampleWhen another Observable firesLatest value at that timePeriodic snapshots
  • auditTime - Control with fixed time (simplified version of audit)
  • throttle - Emit first value at period start
  • debounce - Emit value after silence
  • sample - Sample at timing of another Observable

Summary

The audit operator emits the last value within a period dynamically controlled by a custom Observable.

  • ✅ Dynamic period control possible
  • ✅ Adaptive sampling according to load
  • ✅ Control based on other streams
  • ⚠️ Must generate new Observable each time
  • ⚠️ Be mindful of memory with frequent emissions

Released under the CC-BY-4.0 license.