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 = {}; /// 异步执行方法 - 防止弱网重复点击 (无 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) === // 延迟 duration 后执行,如果在期间再次调用,会重新计时 EasyDebounce.debounce(tagId, duration, () async { // 防抖触发时,再次检查锁(双重检查) if (isExecuting(tagId)) return; _asyncLocks[tagId] = true; try { await onExecute(); } finally { _asyncLocks.remove(tagId); } }); } 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); } } } /// 检查任务是否正在执行 bool isExecuting(String tagId) => _asyncLocks[tagId] ?? false; /// 强制取消所有锁(慎用) void clearAllLocks() => _asyncLocks.clear(); }