异步任务互斥锁工具:解决前端并发控制的优雅方案

49 阅读10分钟

1.0版本+设计思路=> # 异步互斥锁

*设计思路在上一版本代码中,此处不再赘述。 迭代原因:在vue+web中正常使用,但在uniapp+APP/MP环境中,部分web端特有功能无法使用并且报错,导致整个工具函数死区。故为适配uniapp环境,对一些功能进行了妥协性的降级处理。

代码已上传npm,使用pnpm add jtwenty 可下载完整工具包(快速开发迭代中)*

异步任务互斥锁工具:解决前端并发控制的优雅方案

在复杂的前端应用中,如何优雅地控制异步任务的并发执行?一个健壮的互斥锁工具可能是答案。

概述

在前端开发中,我们经常面临异步任务并发控制的问题:表单重复提交、接口竞态条件、资源争用等。虽然防抖和节流技术可以解决部分高频触发问题,但当面对长时间运行的异步任务(如文件上传、复杂计算、多步提交)时,它们就显得力不从心。

Async Lock Manager 是一个专为前端环境设计的异步任务互斥锁工具,它采用互斥锁思想,通过任务名称对异步操作进行智能锁定,防止重复执行,同时提供超时控制、任务取消、队列管理等高级功能。

设计哲学:为什么需要专门的异步锁工具?

传统的前端异步处理方案存在以下局限:

  1. 防抖节流的不足:只能控制触发频率,无法防止长时间异步任务执行期间的重复触发
  2. 缺乏原子性保证:多个异步操作可能同时进入临界区,导致状态不一致
  3. 错误处理薄弱:一个任务失败可能影响整个业务流程
  4. 缺少取消机制:无法中止正在执行的异步操作
  5. 无排队管理:并发请求要么被拒绝,要么同时发送,缺乏有序执行

本工具的设计目标正是为了解决这些问题,提供:

  • 可靠的互斥机制:确保同一时刻只有一个同名任务执行
  • 完善的错误处理:支持超时、取消、队列满等错误类型
  • 灵活的队列管理:支持任务排队和智能调度
  • 跨平台兼容:适配 Web、uni-app 及各种小程序环境
  • 资源自动清理:防止内存泄漏,确保系统稳定性

核心特性

1. 智能锁管理

每个锁通过唯一名称标识,只有获取锁的任务才能执行,后续同名任务会被拒绝或加入队列等待。

2. 超时控制

可配置执行超时时间,防止任务无限期占用锁资源。

3. 任务取消

支持手动取消正在执行的任务,释放锁资源。

4. 队列机制

当锁被占用时,后续任务可选择加入等待队列,按顺序执行。

5. 指数退避重试

对可重试的失败任务采用指数退避算法,避免频繁重试造成服务器压力。

6. 原子操作

通过多层检查确保锁获取的原子性,避免竞态条件。

7. 错误分类

将错误细分为重复执行、超时、取消、队列满等类型,便于针对性处理。

8. 性能监控

内置统计功能,监控任务执行情况,辅助性能优化。

安装与使用

基本用法

import { asyncLock } from './async-lock';

// 提交表单时防止重复提交
const submitForm = async (formData) => {
  try {
    const result = await asyncLock({
      name: 'form-submit',
      asyncFn: async (signal) => {
        // signal可用于监听取消事件(Web环境)
        const response = await fetch('/api/submit', {
          method: 'POST',
          body: JSON.stringify(formData),
          signal // 传入AbortSignal以支持取消
        });
        return await response.json();
      },
      timeout: 8000, // 8秒超时
      repeatTip: '正在提交,请勿重复操作',
      onSuccess: (result) => {
        console.log('提交成功:', result);
      },
      onFail: (error) => {
        if (error.type === 'repeat') {
          // 重复提交错误,通常已通过tipHandler提示用户
          console.warn('请勿重复提交');
        } else if (error.type === 'timeout') {
          console.error('提交超时,请重试');
        }
      }
    });
    return result;
  } catch (error) {
    console.error('表单提交失败:', error);
    throw error;
  }
};

队列功能示例

// 启用队列功能,任务会按顺序执行
const processTasks = async (tasks) => {
  const results = [];
  
  for (const task of tasks) {
    try {
      const result = await asyncLock({
        name: 'sequential-processing',
        asyncFn: async () => {
          return await processSingleTask(task);
        },
        enableQueue: true, // 启用队列
        maxQueueSize: 10,  // 最大队列长度
        timeout: 5000
      });
      results.push(result);
    } catch (error) {
      if (error.code === 'QUEUE_FULL') {
        console.error('系统繁忙,请稍后重试');
        break;
      }
    }
  }
  
  return results;
};

高级配置

import { createLockManager } from './async-lock';

// 创建自定义锁管理器实例
const customLockManager = createLockManager({
  timeout: 10000, // 默认超时10秒
  repeatTip: '操作进行中,请稍候...',
  throwRepeatError: false, // 不抛出重复错误,静默处理
  autoCleanup: true, // 自动清理完成的锁
  maxLockAge: 300000, // 锁最大存在时间5分钟
  maxQueueSize: 20, // 队列最大长度
  tipHandler: (message) => {
    // 自定义提示处理器,可接入UI框架
    showToast(message);
  }
});

// 使用自定义管理器
customLockManager.execute({
  name: 'critical-operation',
  asyncFn: criticalAsyncFunction,
  retryCount: 3, // 失败重试3次
  baseRetryDelay: 1000, // 基础重试延迟1秒
  retryCondition: (error) => {
    // 自定义重试条件:只有网络错误才重试
    return error.message.includes('Network') || error.code === 'NETWORK_ERROR';
  }
});

跨平台适配策略

Web 环境

在 Web 环境中,工具利用标准的 AbortController API 实现真正的请求中断:

// Web环境下使用AbortController
asyncFn: async (signal) => {
  const response = await fetch('/api/data', { signal });
  if (signal.aborted) {
    throw new Error('请求已被取消');
  }
  return response.json();
}

uni-app 及小程序环境

在非 Web 环境中,工具提供降级方案:

// uni-app环境下使用适配器
asyncFn: async (signal) => {
  return new Promise((resolve, reject) => {
    const requestTask = uni.request({
      url: '/api/data',
      success: resolve,
      fail: reject
    });
    
    // 监听取消信号(非Web环境为模拟信号)
    if (signal) {
      const checkAbort = () => {
        if (signal.aborted) {
          requestTask.abort(); // 调用uni-app的abort方法
          reject(signal.reason || new Error('已取消'));
        }
      };
      
      // 对于模拟信号,需要轮询检查
      const intervalId = setInterval(checkAbort, 100);
      
      // 清理函数
      signal._cleanup = () => clearInterval(intervalId);
    }
  });
}

推荐的请求封装模式

// utils/request-with-lock.js
export function createLockableRequest(lockManager) {
  return async function requestWithLock(options) {
    const { lockName, ...requestOptions } = options;
    
    return lockManager.execute({
      name: lockName,
      asyncFn: async (signal) => {
        // 统一处理不同环境的信号
        return await platformSpecificRequest(requestOptions, signal);
      },
      timeout: requestOptions.timeout || 10000,
      enableQueue: true
    });
  };
}

// 平台特定的请求实现
async function platformSpecificRequest(options, signal) {
  // 判断环境并选择对应实现
  if (typeof uni !== 'undefined') {
    // uni-app环境
    return uniAppRequest(options, signal);
  } else {
    // Web环境
    return webRequest(options, signal);
  }
}

实现原理

锁状态管理

工具使用 Map 数据结构存储所有锁的状态,确保 O(1) 时间复杂度的锁查找。每个锁包含以下信息:

  • 锁定状态(locked)
  • 创建时间(createdAt)
  • 任务ID(taskId)
  • 中断控制器(abortController,Web环境)
  • 超时定时器(timeoutTimer)

原子性保证

通过三层检查确保锁获取的原子性:

  1. 初步检查锁状态
  2. 创建锁对象并二次检查
  3. 设置锁后的最终验证

队列管理

使用先进先出(FIFO)队列管理等待任务,支持:

  • 队列长度限制
  • 队列超时处理
  • 智能任务调度

错误分类体系

const ERROR_TYPES = {
  REPEAT: 'repeat',      // 重复执行
  TIMEOUT: 'timeout',    // 执行超时
  CANCEL: 'cancel',      // 任务取消
  QUEUE_FULL: 'queue_full', // 队列已满
  QUEUE_TIMEOUT: 'queue_timeout', // 队列等待超时
  LOCK_FAILED: 'lock_failed' // 获取锁失败
};

应用场景

场景1:表单提交防重复

防止用户在请求未完成时重复提交表单,特别是支付、订单提交等关键操作。

场景2:资源争用控制

管理对共享资源(如编辑权限、配置数据)的访问,确保同一时刻只有一个操作生效。

场景3:批量任务有序执行

控制批量任务(如文件批量上传、数据批量处理)的执行顺序,避免服务器过载。

场景4:实时数据同步

在实时数据更新场景中,防止并发更新导致的数据不一致问题。

完整源码

/**
 * 异步任务互斥锁工具
 * 核心能力:防止异步任务未完成时重复执行、超时控制、任务取消、资源自动清理
 * 支持:队列机制、指数退避重试、原子操作、错误分类、性能监控
 */
class LockManager {
  constructor(options = {}) {
    this._lockMap = new Map();
    this._queueMap = new Map();
    
    this._defaults = {
      timeout: 10000,
      repeatTip: '操作中,请稍后...',
      throwRepeatError: true,
      autoCleanup: true,
      maxLockAge: 5 * 60 * 1000,
      maxQueueSize: 100,
      enableStats: true,
      tipHandler: () => {},
      ...options
    };
    
    this._stats = {
      totalExecutions: 0,
      successCount: 0,
      timeoutCount: 0,
      cancelCount: 0,
      repeatRejectCount: 0,
      queueFullCount: 0,
      retryCount: 0
    };
    
    this._cleanupInterval = setInterval(
      () => this._cleanupExpiredLocks(), 
      60000
    );
    
    this._processNextInQueue = this._processNextInQueue.bind(this);
  }
  
  /**
   * 原子性地获取锁
   */
  _acquireLock(name) {
    const attemptId = `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
    const now = Date.now();
    
    // 第一重检查
    const existing = this._lockMap.get(name);
    if (existing?.locked) {
      return null;
    }
    
    // 创建新的锁对象
    const lockItem = {
      locked: true,
      abortController: new AbortController(),
      timeoutTimer: null,
      createdAt: now,
      taskId: `${name}_${now}_${Math.random().toString(36).slice(2, 10)}`,
      attemptId: attemptId,
      waitingQueue: this._queueMap.get(name) || []
    };
    
    // 第二重检查(原子性保障)
    const current = this._lockMap.get(name);
    if (current?.locked) {
      return null;
    }
    
    // 设置锁(原子操作)
    this._lockMap.set(name, lockItem);
    
    // 最终验证
    const afterSet = this._lockMap.get(name);
    if (afterSet?.attemptId !== attemptId) {
      lockItem.abortController.abort();
      return null;
    }
    
    return lockItem;
  }
  
  /**
   * 执行带锁的异步任务
   */
  async execute(options) {
    const {
      name,
      asyncFn,
      onSuccess,
      onFail,
      repeatTip = this._defaults.repeatTip,
      timeout = this._defaults.timeout,
      throwRepeatError = this._defaults.throwRepeatError,
      tipHandler = this._defaults.tipHandler,
      enableQueue = false,
      maxQueueSize = this._defaults.maxQueueSize,
      retryCount = 0,
      baseRetryDelay = 1000,
      maxRetryDelay = 30000,
      retryCondition = null,
      autoCleanup = this._defaults.autoCleanup
    } = options;
    
    this._stats.totalExecutions++;
    
    try {
      const existingLock = this._lockMap.get(name);
      if (existingLock?.locked) {
        this._stats.repeatRejectCount++;
        
        const repeatError = new Error(repeatTip);
        repeatError.type = 'repeat';
        repeatError.code = 'LOCKED';
        repeatError.lockName = name;
        
        tipHandler(repeatTip);
        
        if (enableQueue) {
          console.log(`任务【${name}】加入等待队列,当前队列长度:${this._queueMap.get(name)?.length || 0}`);
          
          const queueOptions = {
            ...options,
            enableQueue: false,
            maxQueueSize: undefined
          };
          
          const queueResult = await this._addToQueue({
            ...queueOptions,
            name,
            maxQueueSize
          });
          
          onSuccess?.(queueResult);
          return queueResult;
        } else {
          onFail?.(repeatError);
          if (throwRepeatError) throw repeatError;
          return Promise.reject(repeatError);
        }
      }
      
      const result = await this._executeTask({
        name,
        asyncFn,
        timeout,
        retryCount,
        baseRetryDelay,
        maxRetryDelay,
        retryCondition,
        autoCleanup
      });
      
      this._stats.successCount++;
      onSuccess?.(result);
      return result;
    } catch (error) {
      switch (error.type) {
        case 'timeout':
          this._stats.timeoutCount++;
          break;
        case 'cancel':
          this._stats.cancelCount++;
          break;
        case 'queue_full':
          this._stats.queueFullCount++;
          break;
      }
      
      onFail?.(error);
      throw error;
    }
  }
  
  /**
   * 将任务加入等待队列
   */
  _addToQueue(options) {
    const { name, maxQueueSize = this._defaults.maxQueueSize } = options;
    
    let queue = this._queueMap.get(name);
    if (!queue) {
      queue = [];
      this._queueMap.set(name, queue);
    }
    
    if (queue.length >= maxQueueSize) {
      this._stats.queueFullCount++;
      const error = new Error(`任务队列【${name}】已满(最大${maxQueueSize})`);
      error.type = 'queue_full';
      error.code = 'QUEUE_FULL';
      return Promise.reject(error);
    }
    
    return new Promise((resolve, reject) => {
      const queueItem = {
        options,
        resolve,
        reject,
        enqueuedAt: Date.now()
      };
      
      queue.push(queueItem);
      
      if (options.timeout > 0) {
        queueItem.timeoutTimer = setTimeout(() => {
          const index = queue.indexOf(queueItem);
          if (index > -1) {
            queue.splice(index, 1);
            const error = new Error(`任务【${name}】在队列中等待超时`);
            error.type = 'queue_timeout';
            error.code = 'QUEUE_TIMEOUT';
            reject(error);
            
            if (queue.length === 0) {
              this._queueMap.delete(name);
            }
          }
        }, options.timeout);
      }
    });
  }
  
  /**
   * 处理队列中的下一个任务
   */
  async _processNextInQueue(name) {
    const queue = this._queueMap.get(name);
    if (!queue || queue.length === 0) {
      this._queueMap.delete(name);
      return;
    }
    
    await Promise.resolve();
    
    const queueItem = queue.shift();
    
    if (queueItem.timeoutTimer) {
      clearTimeout(queueItem.timeoutTimer);
    }
    
    try {
      const result = await this._executeTask(queueItem.options);
      queueItem.resolve(result);
    } catch (error) {
      queueItem.reject(error);
    } finally {
      if (queue.length > 0) {
        Promise.resolve().then(() => this._processNextInQueue(name));
      } else {
        this._queueMap.delete(name);
      }
    }
  }
  
  /**
   * 执行任务核心逻辑
   */
  async _executeTask(options) {
    const {
      name,
      asyncFn,
      timeout = this._defaults.timeout,
      retryCount = 0,
      baseRetryDelay = 1000,
      maxRetryDelay = 30000,
      retryCondition = null
    } = options;
    
    const lockItem = this._acquireLock(name);
    if (!lockItem) {
      const error = new Error(`无法获取锁【${name}】`);
      error.type = 'lock_failed';
      error.code = 'LOCK_FAILED';
      throw error;
    }
    
    let result;
    try {
      if (timeout > 0) {
        lockItem.timeoutTimer = setTimeout(() => {
          const timeoutError = new Error(`任务【${name}】超时(${timeout}ms)`);
          timeoutError.type = 'timeout';
          timeoutError.code = 'TIMEOUT';
          lockItem.abortController.abort(timeoutError);
        }, timeout);
      }
      
      result = await this._executeWithExponentialBackoff(
        () => asyncFn(lockItem.abortController.signal),
        retryCount,
        baseRetryDelay,
        maxRetryDelay,
        lockItem.abortController,
        retryCondition
      );
      
      return result;
    } catch (error) {
      error.lockName = name;
      error.taskId = lockItem.taskId;
      throw error;
    } finally {
      this._cleanupLock(name, lockItem, options.autoCleanup ?? this._defaults.autoCleanup);
      Promise.resolve().then(() => this._processNextInQueue(name));
    }
  }
  
  /**
   * 指数退避重试执行
   */
  async _executeWithExponentialBackoff(fn, maxRetries, baseDelay, maxDelay, abortController, retryCondition) {
    let lastError;
    
    for (let attempt = 0; attempt <= maxRetries; attempt++) {
      try {
        if (abortController.signal.aborted) {
          const cancelError = new Error('任务已被取消');
          cancelError.type = 'cancel';
          cancelError.code = 'CANCELLED';
          throw cancelError;
        }
        
        if (attempt > 0) {
          const delay = this._calculateExponentialBackoffDelay(
            attempt,
            baseDelay,
            maxDelay
          );
          
          this._stats.retryCount++;
          console.log(`任务重试第${attempt}次,延迟${delay}ms`);
          
          await this._sleep(delay, abortController.signal);
        }
        
        return await fn();
      } catch (error) {
        lastError = error;
        
        if (!this._shouldRetry(error, retryCondition)) {
          throw error;
        }
        
        if (attempt === maxRetries) {
          error.retryAttempts = attempt;
          throw error;
        }
      }
    }
    
    throw lastError;
  }
  
  /**
   * 判断是否应该重试
   */
  _shouldRetry(error, retryCondition) {
    const noRetryTypes = ['cancel', 'timeout', 'queue_full', 'queue_timeout', 'lock_failed'];
    if (noRetryTypes.includes(error.type)) {
      return false;
    }
    
    if (typeof retryCondition === 'function') {
      return retryCondition(error);
    }
    
    return true;
  }
  
  /**
   * 计算指数退避延迟
   */
  _calculateExponentialBackoffDelay(attempt, baseDelay, maxDelay) {
    const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
    const jitter = exponentialDelay * 0.1 * Math.random();
    return Math.min(exponentialDelay + jitter, maxDelay);
  }
  
  /**
   * 可中断的延时
   */
  _sleep(ms, signal) {
    return new Promise((resolve, reject) => {
      if (signal.aborted) {
        reject(new Error('等待被中断'));
        return;
      }
      
      const timer = setTimeout(() => {
        signal.removeEventListener('abort', abortHandler);
        resolve();
      }, ms);
      
      const abortHandler = () => {
        clearTimeout(timer);
        const error = new Error('等待被中断');
        error.type = 'cancel';
        error.code = 'SLEEP_CANCELLED';
        reject(error);
      };
      
      signal.addEventListener('abort', abortHandler);
      
      const cleanup = () => {
        clearTimeout(timer);
        signal.removeEventListener('abort', abortHandler);
      };
      
      this._safeFinally(() => {
        cleanup();
      }, resolve, reject);
    });
  }
  
  /**
   * 安全的finally执行
   */
  _safeFinally(cleanupFn, resolve, reject) {
    const wrappedResolve = (value) => {
      try {
        cleanupFn();
      } finally {
        resolve(value);
      }
    };
    
    const wrappedReject = (error) => {
      try {
        cleanupFn();
      } finally {
        reject(error);
      }
    };
    
    return { resolve: wrappedResolve, reject: wrappedReject };
  }
  
  /**
   * 清理锁资源
   */
  _cleanupLock(name, lockItem, autoCleanup) {
    if (lockItem.timeoutTimer) {
      clearTimeout(lockItem.timeoutTimer);
      lockItem.timeoutTimer = null;
    }
    
    if (lockItem.abortController) {
      lockItem.abortController = null;
    }
    
    if (autoCleanup) {
      this._lockMap.delete(name);
    } else {
      lockItem.locked = false;
      lockItem.abortController = null;
      lockItem.timeoutTimer = null;
    }
  }
  
  /**
   * 清理过期锁和队列
   */
  _cleanupExpiredLocks() {
    const now = Date.now();
    const maxAge = this._defaults.maxLockAge;
    
    // 清理过期锁
    for (const [name, lockItem] of this._lockMap.entries()) {
      if (lockItem.locked && (now - lockItem.createdAt) > maxAge) {
        console.warn(`清理过期锁【${name}】,已锁定${now - lockItem.createdAt}ms`);
        
        const error = new Error('锁过期自动清理');
        error.type = 'timeout';
        error.code = 'LOCK_EXPIRED';
        
        if (lockItem.abortController) {
          lockItem.abortController.abort(error);
        }
        
        this._lockMap.delete(name);
      }
    }
    
    // 清理过期队列项
    for (const [name, queue] of this._queueMap.entries()) {
      const expiredItems = [];
      
      for (let i = 0; i < queue.length; i++) {
        const item = queue[i];
        const queueAge = now - item.enqueuedAt;
        const timeout = item.options?.timeout || 30000;
        if (queueAge > timeout) {
          expiredItems.push(i);
        }
      }
      
      for (let i = expiredItems.length - 1; i >= 0; i--) {
        const index = expiredItems[i];
        const item = queue[index];
        
        if (item.timeoutTimer) {
          clearTimeout(item.timeoutTimer);
        }
        
        const error = new Error(`任务【${name}】在队列中过期`);
        error.type = 'queue_timeout';
        error.code = 'QUEUE_TIMEOUT';
        item.reject(error);
        
        queue.splice(index, 1);
      }
      
      if (queue.length === 0) {
        this._queueMap.delete(name);
      }
    }
  }
  
  /**
   * 手动释放指定锁
   */
  releaseLock(name) {
    const lockItem = this._lockMap.get(name);
    if (lockItem) {
      this._cleanupLock(name, lockItem, true);
    }
    
    const queue = this._queueMap.get(name);
    if (queue) {
      queue.forEach(item => {
        if (item.timeoutTimer) {
          clearTimeout(item.timeoutTimer);
        }
        const error = new Error('锁被手动释放,队列任务取消');
        error.type = 'cancel';
        error.code = 'MANUAL_RELEASE';
        item.reject(error);
      });
      this._queueMap.delete(name);
    }
  }
  
  /**
   * 批量释放所有锁
   */
  releaseAllLocks() {
    this._lockMap.forEach((lockItem, name) => {
      this._cleanupLock(name, lockItem, true);
    });
    this._lockMap.clear();
    
    this._queueMap.forEach((queue, name) => {
      queue.forEach(item => {
        if (item.timeoutTimer) {
          clearTimeout(item.timeoutTimer);
        }
        const error = new Error('所有锁被释放,队列任务取消');
        error.type = 'cancel';
        error.code = 'ALL_RELEASED';
        item.reject(error);
      });
    });
    this._queueMap.clear();
  }
  
  /**
   * 取消正在执行的任务
   */
  cancelLockTask(name, reason = "用户主动取消") {
    const lockItem = this._lockMap.get(name);
    if (lockItem?.locked && lockItem.abortController) {
      const error = new Error(reason);
      error.type = 'cancel';
      error.code = 'USER_CANCEL';
      lockItem.abortController.abort(error);
      this._cleanupLock(name, lockItem, true);
      return true;
    }
    return false;
  }
  
  /**
   * 获取指定任务的锁状态
   */
  getLockStatus(name) {
    const lockItem = this._lockMap.get(name);
    const queue = this._queueMap.get(name);
    
    return {
      locked: lockItem?.locked ?? false,
      taskId: lockItem?.taskId,
      createdAt: lockItem?.createdAt,
      age: lockItem ? Date.now() - lockItem.createdAt : 0,
      hasAbortController: !!lockItem?.abortController,
      queueLength: queue?.length || 0,
      queueWaitTimes: queue?.map(item => Date.now() - item.enqueuedAt) || []
    };
  }
  
  /**
   * 获取统计信息
   */
  getStats() {
    return {
      ...this._stats,
      activeLocks: Array.from(this._lockMap.entries())
        .filter(([_, lock]) => lock.locked)
        .map(([name, lock]) => ({
          name,
          age: Date.now() - lock.createdAt,
          taskId: lock.taskId
        })),
      waitingQueues: Array.from(this._queueMap.entries())
        .map(([name, queue]) => ({
          name,
          length: queue.length,
          oldestWait: queue.length > 0 ? Date.now() - queue[0].enqueuedAt : 0
        }))
    };
  }
  
  /**
   * 重置统计信息
   */
  resetStats() {
    this._stats = {
      totalExecutions: 0,
      successCount: 0,
      timeoutCount: 0,
      cancelCount: 0,
      repeatRejectCount: 0,
      queueFullCount: 0,
      retryCount: 0
    };
  }
  
  /**
   * 销毁实例
   */
  destroy() {
    clearInterval(this._cleanupInterval);
    this.releaseAllLocks();
    this._queueMap.clear();
    this._lockMap.clear();
  }
}

// 创建锁管理器的工厂函数
export const createLockManager = (options) => new LockManager(options);

// 默认单例
export const defaultLockManager = new LockManager({
  tipHandler: () => {}
});

// 带控制台警告的单例
export const verboseLockManager = new LockManager({
  tipHandler: console.warn
});

// 核心方法导出(使用默认单例)
export const asyncLock = (options) => defaultLockManager.execute(options);
export const releaseLock = (name) => defaultLockManager.releaseLock(name);
export const releaseAllLocks = () => defaultLockManager.releaseAllLocks();
export const cancelLockTask = (name, reason) => defaultLockManager.cancelLockTask(name, reason);
export const getLockStatus = (name) => defaultLockManager.getLockStatus(name);
export const getStats = () => defaultLockManager.getStats();
export const resetStats = () => defaultLockManager.resetStats();
export const destroyLockManager = () => defaultLockManager.destroy();

// 导出类本身
export { LockManager };

总结

异步任务互斥锁工具是前端复杂应用中的重要基础设施,它解决了传统防抖节流无法处理的异步并发控制问题。通过合理的锁机制、队列管理和错误处理,可以显著提升应用的稳定性和用户体验。

在实际项目中,建议根据具体需求选择合适的配置,并确保在 uni-app 等跨平台环境中正确适配请求中断机制。对于简单的场景,可以使用默认单例;对于复杂场景,可以创建多个独立的锁管理器实例,分别管理不同业务域的异步任务。

扩展思考:随着前端应用复杂度的增加,类似的状态管理和并发控制工具将变得越来越重要。未来可以考虑将此工具与状态管理库(如 Pinia、Redux)深度集成,或开发可视化监控界面,进一步提升开发体验和系统可观测性。