167 lines
3.8 KiB
Dart
167 lines
3.8 KiB
Dart
import 'dart:async';
|
|
|
|
enum RateLimitMode { debounce, throttle }
|
|
|
|
class ThrottleOptions {
|
|
const ThrottleOptions({this.leading = true, this.trailing = true});
|
|
|
|
final bool leading;
|
|
final bool trailing;
|
|
}
|
|
|
|
class DebounceThrottle<T> {
|
|
DebounceThrottle({
|
|
required this.duration,
|
|
required this.mode,
|
|
this.onCallback,
|
|
this.throttleOptions = const ThrottleOptions(),
|
|
});
|
|
|
|
final Duration duration;
|
|
final RateLimitMode mode;
|
|
final void Function(T value)? onCallback;
|
|
final ThrottleOptions throttleOptions;
|
|
|
|
Timer? _timer;
|
|
T? _lastValue;
|
|
DateTime? _lastExecuteTime;
|
|
bool _waitingTrailing = false;
|
|
|
|
void call(T value) {
|
|
_lastValue = value;
|
|
switch (mode) {
|
|
case RateLimitMode.debounce:
|
|
_debounce(value);
|
|
case RateLimitMode.throttle:
|
|
_throttle(value);
|
|
}
|
|
}
|
|
|
|
void _debounce(T value) {
|
|
_timer?.cancel();
|
|
_timer = Timer(duration, () {
|
|
onCallback?.call(value);
|
|
_lastExecuteTime = DateTime.now();
|
|
});
|
|
}
|
|
|
|
void _throttle(T value) {
|
|
final now = DateTime.now();
|
|
final last = _lastExecuteTime;
|
|
final inWindow = last != null && now.difference(last) < duration;
|
|
|
|
if (!inWindow && throttleOptions.leading) {
|
|
onCallback?.call(value);
|
|
_lastExecuteTime = now;
|
|
_waitingTrailing = false;
|
|
_timer?.cancel();
|
|
return;
|
|
}
|
|
|
|
if (!throttleOptions.trailing || _waitingTrailing) return;
|
|
|
|
_waitingTrailing = true;
|
|
final remaining = last == null ? duration : duration - now.difference(last);
|
|
_timer?.cancel();
|
|
_timer = Timer(remaining, () {
|
|
final value = _lastValue;
|
|
if (value != null) onCallback?.call(value);
|
|
_lastExecuteTime = DateTime.now();
|
|
_waitingTrailing = false;
|
|
});
|
|
}
|
|
|
|
void flush() {
|
|
_timer?.cancel();
|
|
final value = _lastValue;
|
|
if (value != null) {
|
|
onCallback?.call(value);
|
|
_lastExecuteTime = DateTime.now();
|
|
}
|
|
_lastValue = null;
|
|
_waitingTrailing = false;
|
|
}
|
|
|
|
void cancel() {
|
|
_timer?.cancel();
|
|
_lastValue = null;
|
|
_waitingTrailing = false;
|
|
}
|
|
|
|
void dispose() => cancel();
|
|
}
|
|
|
|
class RateLimitHub {
|
|
RateLimitHub({this.removeDebouncerOnDone = true});
|
|
|
|
final bool removeDebouncerOnDone;
|
|
final Map<Object, DebounceThrottle<dynamic>> _debouncers = {};
|
|
final Map<Object, DebounceThrottle<dynamic>> _throttlers = {};
|
|
|
|
void debounce<T>({
|
|
required Object key,
|
|
required T value,
|
|
Duration duration = const Duration(milliseconds: 300),
|
|
required void Function(T value) onCallback,
|
|
}) {
|
|
_debouncers[key]?.cancel();
|
|
|
|
late DebounceThrottle<T> limiter;
|
|
limiter = DebounceThrottle<T>(
|
|
duration: duration,
|
|
mode: RateLimitMode.debounce,
|
|
onCallback: (value) {
|
|
onCallback(value);
|
|
if (removeDebouncerOnDone) _debouncers.remove(key);
|
|
},
|
|
);
|
|
|
|
_debouncers[key] = limiter;
|
|
limiter(value);
|
|
}
|
|
|
|
void throttle<T>({
|
|
required Object key,
|
|
required T value,
|
|
Duration duration = const Duration(milliseconds: 300),
|
|
ThrottleOptions options = const ThrottleOptions(),
|
|
required void Function(T value) onCallback,
|
|
}) {
|
|
_throttlers.putIfAbsent(
|
|
key,
|
|
() =>
|
|
DebounceThrottle<T>(
|
|
duration: duration,
|
|
mode: RateLimitMode.throttle,
|
|
throttleOptions: options,
|
|
onCallback: onCallback,
|
|
)
|
|
as DebounceThrottle<dynamic>,
|
|
);
|
|
|
|
(_throttlers[key] as DebounceThrottle<T>)(value);
|
|
}
|
|
|
|
void cancel(Object key) {
|
|
_debouncers.remove(key)?.cancel();
|
|
_throttlers.remove(key)?.cancel();
|
|
}
|
|
|
|
void clear() {
|
|
for (final limiter in _debouncers.values) {
|
|
limiter.dispose();
|
|
}
|
|
for (final limiter in _throttlers.values) {
|
|
limiter.dispose();
|
|
}
|
|
_debouncers.clear();
|
|
_throttlers.clear();
|
|
}
|
|
}
|
|
|
|
class RateLimit {
|
|
RateLimit._();
|
|
|
|
static final instance = RateLimitHub();
|
|
}
|