import 'dart:async'; import 'package:easy_debounce/easy_debounce.dart'; import 'package:easy_debounce/easy_throttle.dart'; /// 异步防抖/节流工具类 class AsyncThrottle { // 私有构造函数,防止外部实例化 AsyncThrottle._(); // 懒汉式单例 - 使用 late final 延迟初始化 static final AsyncThrottle _instance = AsyncThrottle._(); // 获取单例实例 static AsyncThrottle get instance => _instance; // 存储异步任务锁状态 final Map _asyncLocks = {}; // 存储防抖模式下的 Completer,确保被取消的任务能正确结束 await final Map> _debounceCompleters = {}; /// 异步执行方法 - 防止弱网重复点击 (无 Loading UI) /// /// 结合了 时间策略(防抖/节流)和 异步任务状态锁。 /// 只有当满足以下两个条件时才会执行: /// 1. 当前没有正在执行的同名任务 (Task Lock - 解决弱网长耗时问题) /// 2. 满足时间策略 (Time Policy - 解决快速连点问题) /// /// [tagId]: 唯一标识符 /// [onExecute]: 要执行的异步方法 /// [duration]: 时间间隔,默认 300ms /// [enableDebounce]: 是否启用防抖模式。 /// - true: 使用防抖 (Debounce) - 延迟执行,最后一次点击生效(适合搜索、输入) /// - false: 使用节流 (Throttle) - 立即执行,忽略后续点击(默认,适合按钮点击) Future execute( String tagId, Future Function() onExecute, { Duration duration = const Duration(milliseconds: 300), bool enableDebounce = false, }) async { // 1. 检查异步锁 (防止上一个请求未回来时重复点击) // 无论是防抖还是节流,只要任务还在执行中,都不应重入 if (isExecuting(tagId)) return; if (enableDebounce) { // === 防抖模式 (Debounce) === // 关键修复:检查是否有正在等待的防抖请求。 // 如果有,说明它被本次新请求“顶掉”了(EasyDebounce 会取消上一个 Timer)。 // 我们需要手动完成它,否则上一次的 await 将永远挂起,导致内存泄漏。 if (_debounceCompleters.containsKey(tagId)) { _completeDebounce(tagId); } final completer = Completer(); _debounceCompleters[tagId] = completer; EasyDebounce.debounce(tagId, duration, () async { // 双重检查:确保 Completer 还在(防止极端并发情况) if (!_debounceCompleters.containsKey(tagId)) return; // 防抖触发时,再次检查锁 if (isExecuting(tagId)) { _completeDebounce(tagId); return; } _asyncLocks[tagId] = true; try { await onExecute(); } catch (e, stack) { // 这里必须捕获并转发到 execute() 返回的 Future。 // 否则异常会在 Timer 回调中变成未捕获异步异常(unhandled async error),影响 UI。 _completeDebounce(tagId, error: e, stackTrace: stack); } finally { _asyncLocks.remove(tagId); _completeDebounce(tagId); // 任务结束,通知 await 返回 } }); await completer.future; } else { // === 节流模式 (Throttle) - 默认 === // 立即执行,并在 duration 内忽略后续调用 // throttle 返回 true 表示被节流(忽略),false 表示获得了执行权 final isThrottled = EasyThrottle.throttle(tagId, duration, () {}); // 如果被节流了,直接返回 if (isThrottled) return; // 获得执行权,加锁并执行 _asyncLocks[tagId] = true; try { await onExecute(); } finally { _asyncLocks.remove(tagId); } } } /// UI 安全版:用于 onTap/onPressed 等无法 await 的场景。 /// /// - 永远不会向外抛异常(避免影响 UI / 触发全局未捕获异常) /// - 可通过 [onError] 收集日志/上报/Toast Future executeSafe( String tagId, Future Function() onExecute, { Duration duration = const Duration(milliseconds: 300), bool enableDebounce = false, void Function(Object error, StackTrace stackTrace)? onError, }) { return execute(tagId, onExecute, duration: duration, enableDebounce: enableDebounce).catchError(( Object e, StackTrace s, ) { onError?.call(e, s); }); } /// 辅助方法:安全地完成并移除 Completer void _completeDebounce(String tagId, {Object? error, StackTrace? stackTrace}) { if (_debounceCompleters.containsKey(tagId)) { final completer = _debounceCompleters[tagId]; if (completer != null && !completer.isCompleted) { if (error != null) { completer.completeError(error, stackTrace); } else { completer.complete(); } } _debounceCompleters.remove(tagId); } } /// 检查任务是否正在执行 bool isExecuting(String tagId) => _asyncLocks[tagId] ?? false; /// 强制取消所有锁(慎用) void clearAllLocks() { _asyncLocks.clear(); // 清理时把所有等待的 Future 也都释放掉,防止外部 await 永久挂起 for (var key in _debounceCompleters.keys.toList()) { _completeDebounce(key); } } }