6

Is there a Streams equivalent to Observable.Throttle? If not -- is there any reasonably elegant way to achieve a similar effect?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
w.brian
  • 16,296
  • 14
  • 69
  • 118

5 Answers5

5

There's no such method on streams for now. A enhancement request has been filed, you can star issue 8492.

However, you can do that with the where method. In the following exemple, I have defined a ThrottleFilter class to ignore events during a given duration :

import 'dart:async';

class ThrottleFilter<T> {
  DateTime lastEventDateTime = null;
  final Duration duration;

  ThrottleFilter(this.duration);

  bool call(T e) {
    final now = new DateTime.now();
    if (lastEventDateTime == null ||
        now.difference(lastEventDateTime) > duration) {
      lastEventDateTime = now;
      return true;
    }
    return false;
  }
}

main() {
  final sc = new StreamController<int>();
  final stream = sc.stream;

  // filter stream with ThrottleFilter
  stream.where(new ThrottleFilter<int>(const Duration(seconds: 10)).call)
    .listen(print);

  // send ints to stream every second, but ThrottleFilter will give only one int
  // every 10 sec.
  int i = 0;
  new Timer.repeating(const Duration(seconds:1), (t) { sc.add(i++); });
}
Alexandre Ardhuin
  • 71,959
  • 15
  • 151
  • 132
2

The following version is closer to what Observable.Throttle does:

class Throttle extends StreamEventTransformer {
  final duration;
  Timer lastTimer;

  Throttle(millis) :
    duration = new Duration(milliseconds : millis);


  void handleData(event, EventSink<int> sink) {
    if(lastTimer != null){
      lastTimer.cancel();
    }
    lastTimer = new Timer(duration, () => sink.add(event));
  }
}

main(){
  //...
  stream.transform(new Throttle(500)).listen((_) => print(_));
  //..
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Victor Savkin
  • 1,749
  • 1
  • 12
  • 17
2

The rate_limit package provides throttling and debouncing of Streams.

Sean Eagan
  • 1,130
  • 7
  • 9
2

@Victor Savkin's answer is nice, but I always try to avoid reinventing the wheel. So unless you really only need that throttle I'd suggest using the RxDart package. Since you are dealing with Streams and other reactive objects RxDart has a lot of nice goodies to offer besides throttling.

We can achieve a 500 millisecond throttle several ways:

  1. throttleTime from ThrottleExtensions<T> Stream<T> extensions: stream.throttleTime(Duration(milliseconds: 500)).listen(print);
  2. Combining ThrottleStreamTransformer with TimerStream: stream.transform(ThrottleStreamTransformer((_) => TimerStream(true, const Duration(milliseconds: 500)))).listen(print);
  3. Using Debounce Extensions / DebounceStreamTransformer: stream.debounceTime(Duration(milliseconds: 500)).listen(print);

There are some subtle differences regarding delays, but all of them throttles. As an example about throttleTime vs. debounceTime see What is the difference between throttleTime vs debounceTime in RxJS and when to choose which?

Csaba Toth
  • 10,021
  • 5
  • 75
  • 121
0

Here are 2 solutions that do not require additional packages:

1. Extension Method

import 'dart.async';

extension StreamMethods<T> on Stream<T> {
  Stream<T> throttle(Duration duration) {
    T? lastEvent;
    Timer? throttleTimer;
    StreamController<T> resultStreamController = StreamController<T>();
    
    listen((event) {
      lastEvent = event;
      throttleTimer ??= Timer(
        duration,
        () {
          throttleTimer = null;
          if (lastEvent != null) {
            resultStreamController.add(lastEvent!);
            lastEvent = null;
          }
        },
      );
    });

    return resultStreamController.stream;
  }
}

void main() {
  // this will output an 20 events every second: 1, 2, 3, ...
  final stream = Stream.periodic(Duration(seconds: 1), (i) => i + 1).take(20);
  
  // this will throttle events every 3 seconds: 4, 8, 12, ...
  stream.throttle(Duration(seconds: 3)).listen(print);
}

2. Implementing StreamTransformer

I believe this would be an up-to-date version of @Victor Savkin's answer:

import 'dart:async';

class Throttle<S, T> implements StreamTransformer<S, T> {
  final _resultStreamController = StreamController<T>();
  Timer? throttleTimer;
  Duration duration;
  
  Throttle(this.duration);
  
  @override
  Stream<T> bind(Stream<S> stream) {
    S? lastEvent;
    
    stream.listen((event) {
      lastEvent = event;
      throttleTimer ??= Timer(
        Duration(seconds: 3),
        () {
          throttleTimer = null;
          if (lastEvent != null) {
            _resultStreamController.add(lastEvent! as T);
            lastEvent = null;
          }
        },
      );
    });
    
    return _resultStreamController.stream;
  }
  
  @override
  StreamTransformer<RS, RT> cast<RS, RT>() {
    return StreamTransformer.castFrom(this);
  }
}

void main() {
  // this will output an 20 events every second: 1, 2, 3, ...
  final stream = Stream.periodic(Duration(seconds: 1), (i) => i + 1).take(20);

  // this will throttle events every 3 seconds: 4, 8, 12, ...
  stream.transform(Throttle(Duration(seconds: 3))).listen(print);
}
ANDREYDEN
  • 96
  • 10