You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxJS中timer/interval间隔不准,如何实现精确100ms发射的Observable?

Fixing Drifting Intervals in RxJS Observable

Hey there! I totally get the frustration here—when you set a 100ms interval, you expect it to be consistent, not drifting a few ms each time. Let me break down why this happens and how you can fix it.

Why the Drift Happens

JavaScript runs on a single-threaded event loop. Both RxJS's timer and interval methods don't execute their emissions exactly at the specified time—they queue the next emission to run when the event loop is free. If there's any other work happening (like browser rendering, garbage collection, or other callbacks), your observable's emission gets delayed, leading to those extra milliseconds you're seeing. Your measurement code using Date.now() is correctly capturing this drift.

A Drift-Compensating Observable

To fix this, we can create a custom Observable that tracks the expected emission time and adjusts the next delay to compensate for any drift. Here's a TypeScript implementation:

import { Observable } from 'rxjs';

function preciseInterval(initialDelay: number, targetInterval: number) {
  return new Observable<number>(observer => {
    let emissionCount = 0;
    // Calculate the first expected emission time
    let nextExpectedTime = Date.now() + initialDelay;

    const emitNext = () => {
      const currentTime = Date.now();
      // Emit the count
      observer.next(emissionCount++);
      // Update the next expected time (add target interval, not current delay)
      nextExpectedTime += targetInterval;
      // Calculate how long to wait until the next expected time
      // Use Math.max(0) to avoid negative delays (if we're already past the expected time)
      const delay = Math.max(0, nextExpectedTime - currentTime);
      // Schedule the next emission
      setTimeout(emitNext, delay);
    };

    // Schedule the first emission
    const timeoutId = setTimeout(emitNext, initialDelay);

    // Cleanup function to cancel the timeout when unsubscribed
    return () => clearTimeout(timeoutId);
  });
}

How to Use It

Replace your original timer call with this custom observable:

import { take } from 'rxjs/operators';

const obsTime = preciseInterval(0, 100).pipe(take(30));
let t0 = Date.now();
const timeIntervals: number[] = [];

obsTime.subscribe({
  next: () => {
    const t1 = Date.now();
    if (timeIntervals.length > 0) {
      timeIntervals.push(t1 - t0);
      console.log(`Interval: ${timeIntervals[timeIntervals.length - 1]}ms`);
    }
    t0 = t1;
  },
  complete: () => {
    const average = timeIntervals.reduce((sum, interval) => sum + interval, 0) / timeIntervals.length;
    console.log(`Average interval: ${average.toFixed(2)}ms`);
  }
});

Key Notes

  • This approach won't give you perfect precision (nothing in JavaScript can, due to the single-threaded event loop), but it will keep the average interval extremely close to your target 100ms by correcting drift on each emission.
  • If you have long-running synchronous code blocking the main thread, even this method will struggle. To avoid this, offload heavy tasks to Web Workers (in browsers) or worker threads (in Node.js).
  • In Node.js, you could also use setImmediate for faster scheduling, but the core drift-compensation logic remains the same.

内容的提问来源于stack exchange,提问作者Picci

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:41:51