🚫 请求取消还在用flag?AbortController让你的异步操作更优雅

🎯 学习目标:掌握AbortController的核心API和实际应用场景,学会优雅地取消异步操作

📊 难度等级 :初级-中级

🏷️ 技术标签#AbortController #异步操作 #请求取消 #JavaScript

⏱️ 阅读时间:约8分钟


🌟 引言

在日常的前端开发中,你是否遇到过这样的困扰:

  • 用户快速切换页面:上一个页面的请求还在进行,新页面又发起了请求,造成资源浪费
  • 搜索框防抖优化:用户快速输入时,之前的搜索请求应该被取消
  • 组件卸载时:异步操作还在进行,可能导致内存泄漏或状态更新错误
  • 超时控制:长时间的请求需要主动取消,提升用户体验
  • 并发请求管理:多个相同请求同时发起,需要取消重复的请求

传统的做法是使用各种flag标记来判断是否继续执行,但这种方式不够优雅且容易出错。今天分享AbortController的多个核心应用场景,让你的异步操作管理更加专业!


💡 核心技巧详解

1. 基础用法:取消fetch请求

🔍 应用场景

最常见的场景就是取消HTTP请求,特别是在用户快速操作或组件卸载时。

❌ 传统做法

javascript 复制代码
// ❌ 使用flag标记的传统写法
let isCancelled = false;

const fetchData = async () => {
  try {
    const response = await fetch('/api/data');
    if (isCancelled) return; // 手动检查取消状态
    
    const data = await response.json();
    if (isCancelled) return; // 每个异步操作后都要检查
    
    updateUI(data);
  } catch (error) {
    if (!isCancelled) {
      console.error('请求失败:', error);
    }
  }
};

// 取消请求
const cancelRequest = () => {
  isCancelled = true;
};

✅ 推荐方案

javascript 复制代码
/**
 * 使用AbortController取消fetch请求
 * @description 创建可取消的HTTP请求
 * @returns {Object} 包含请求方法和取消方法的对象
 */
const createCancellableRequest = () => {
  const controller = new AbortController();
  
  const fetchData = async () => {
    try {
      const response = await fetch('/api/data', {
        signal: controller.signal // 传入取消信号
      });
      
      const data = await response.json();
      updateUI(data);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('请求已取消');
      } else {
        console.error('请求失败:', error);
      }
    }
  };
  
  return {
    fetchData,
    cancel: () => controller.abort()
  };
};

💡 核心要点

  • signal属性:将controller.signal传递给fetch的options
  • AbortError:取消操作会抛出name为'AbortError'的错误
  • 自动清理:无需手动管理状态标记

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const { fetchData, cancel } = createCancellableRequest();

// 发起请求
fetchData();

// 5秒后自动取消
setTimeout(() => {
  cancel();
}, 5000);

2. 搜索防抖:取消过期搜索请求

🔍 应用场景

用户在搜索框快速输入时,需要取消之前的搜索请求,只保留最新的搜索。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的搜索功能
 * @description 实现搜索防抖和请求取消
 */
class SearchManager {
  constructor() {
    this.currentController = null;
    this.debounceTimer = null;
  }
  
  /**
   * 执行搜索
   * @param {string} keyword - 搜索关键词
   * @param {number} delay - 防抖延迟时间
   */
  search = (keyword, delay = 300) => {
    // 清除之前的防抖定时器
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
    
    // 取消之前的请求
    if (this.currentController) {
      this.currentController.abort();
    }
    
    this.debounceTimer = setTimeout(() => {
      this.performSearch(keyword);
    }, delay);
  };
  
  /**
   * 执行实际搜索
   * @param {string} keyword - 搜索关键词
   */
  performSearch = async (keyword) => {
    if (!keyword.trim()) return;
    
    // 创建新的控制器
    this.currentController = new AbortController();
    
    try {
      const response = await fetch(`/api/search?q=${encodeURIComponent(keyword)}`, {
        signal: this.currentController.signal
      });
      
      const results = await response.json();
      this.displayResults(results);
    } catch (error) {
      if (error.name !== 'AbortError') {
        console.error('搜索失败:', error);
      }
    }
  };
  
  /**
   * 显示搜索结果
   * @param {Array} results - 搜索结果
   */
  displayResults = (results) => {
    const resultsContainer = document.getElementById('search-results');
    resultsContainer.innerHTML = results.map(item => 
      `<div class="result-item">${item.title}</div>`
    ).join('');
  };
  
  /**
   * 清理资源
   */
  destroy = () => {
    if (this.currentController) {
      this.currentController.abort();
    }
    if (this.debounceTimer) {
      clearTimeout(this.debounceTimer);
    }
  };
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const searchManager = new SearchManager();

// 绑定搜索框事件
document.getElementById('search-input').addEventListener('input', (e) => {
  searchManager.search(e.target.value);
});

// 页面卸载时清理
window.addEventListener('beforeunload', () => {
  searchManager.destroy();
});

3. 超时控制:自动取消超时请求

🔍 应用场景

为请求设置超时时间,超时后自动取消,避免长时间等待。

✅ 推荐方案

javascript 复制代码
/**
 * 带超时控制的请求函数
 * @description 创建具有超时自动取消功能的请求
 * @param {string} url - 请求地址
 * @param {Object} options - 请求选项
 * @param {number} timeout - 超时时间(毫秒)
 * @returns {Promise} 请求Promise
 */
const fetchWithTimeout = async (url, options = {}, timeout = 5000) => {
  const controller = new AbortController();
  
  // 设置超时定时器
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeout);
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    
    // 清除超时定时器
    clearTimeout(timeoutId);
    
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    
    if (error.name === 'AbortError') {
      throw new Error(`请求超时 (${timeout}ms)`);
    }
    throw error;
  }
};

/**
 * 高级超时控制类
 * @description 提供更灵活的超时控制功能
 */
class TimeoutController {
  constructor(timeout = 5000) {
    this.timeout = timeout;
    this.controller = new AbortController();
    this.timeoutId = null;
  }
  
  /**
   * 开始超时计时
   * @param {Function} onTimeout - 超时回调
   */
  start = (onTimeout) => {
    this.timeoutId = setTimeout(() => {
      this.controller.abort();
      if (onTimeout) onTimeout();
    }, this.timeout);
  };
  
  /**
   * 取消超时
   */
  cancel = () => {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
      this.timeoutId = null;
    }
  };
  
  /**
   * 手动中止
   */
  abort = () => {
    this.cancel();
    this.controller.abort();
  };
  
  /**
   * 获取信号
   */
  get signal() {
    return this.controller.signal;
  }
}

🎯 实际应用

javascript 复制代码
// 基础用法
try {
  const response = await fetchWithTimeout('/api/slow-endpoint', {}, 3000);
  const data = await response.json();
  console.log('数据获取成功:', data);
} catch (error) {
  console.error('请求失败:', error.message);
}

// 高级用法
const timeoutController = new TimeoutController(5000);

timeoutController.start(() => {
  console.log('请求超时了!');
});

try {
  const response = await fetch('/api/data', {
    signal: timeoutController.signal
  });
  timeoutController.cancel(); // 成功后取消超时
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('请求被取消');
  }
}

4. 组件生命周期:React/Vue中的请求管理

🔍 应用场景

在React或Vue组件中,当组件卸载时需要取消正在进行的异步操作。

✅ React Hook实现

javascript 复制代码
/**
 * 可取消的异步操作Hook
 * @description React Hook,自动管理组件生命周期中的异步操作
 * @returns {Object} 包含执行和取消方法的对象
 */
import { useEffect, useRef, useCallback } from 'react';

const useCancellableAsync = () => {
  const controllerRef = useRef(null);
  
  /**
   * 执行可取消的异步操作
   * @param {Function} asyncFn - 异步函数
   * @returns {Promise} 异步操作Promise
   */
  const execute = useCallback(async (asyncFn) => {
    // 取消之前的操作
    if (controllerRef.current) {
      controllerRef.current.abort();
    }
    
    // 创建新的控制器
    controllerRef.current = new AbortController();
    
    try {
      return await asyncFn(controllerRef.current.signal);
    } catch (error) {
      if (error.name !== 'AbortError') {
        throw error;
      }
    }
  }, []);
  
  /**
   * 手动取消操作
   */
  const cancel = useCallback(() => {
    if (controllerRef.current) {
      controllerRef.current.abort();
      controllerRef.current = null;
    }
  }, []);
  
  // 组件卸载时自动取消
  useEffect(() => {
    return () => {
      cancel();
    };
  }, [cancel]);
  
  return { execute, cancel };
};

// 使用示例
const DataComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const { execute, cancel } = useCancellableAsync();
  
  const fetchData = async () => {
    setLoading(true);
    try {
      await execute(async (signal) => {
        const response = await fetch('/api/data', { signal });
        const result = await response.json();
        setData(result);
      });
    } catch (error) {
      console.error('获取数据失败:', error);
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <div>
      <button onClick={fetchData}>获取数据</button>
      <button onClick={cancel}>取消请求</button>
      {loading && <div>加载中...</div>}
      {data && <div>{JSON.stringify(data)}</div>}
    </div>
  );
};

✅ Vue Composition API实现

javascript 复制代码
/**
 * Vue可取消异步操作组合函数
 * @description Vue3 Composition API,管理组件中的异步操作
 */
import { ref, onUnmounted } from 'vue';

const useCancellableAsync = () => {
  const controller = ref(null);
  
  /**
   * 执行可取消的异步操作
   * @param {Function} asyncFn - 异步函数
   */
  const execute = async (asyncFn) => {
    // 取消之前的操作
    if (controller.value) {
      controller.value.abort();
    }
    
    // 创建新的控制器
    controller.value = new AbortController();
    
    try {
      return await asyncFn(controller.value.signal);
    } catch (error) {
      if (error.name !== 'AbortError') {
        throw error;
      }
    }
  };
  
  /**
   * 取消操作
   */
  const cancel = () => {
    if (controller.value) {
      controller.value.abort();
      controller.value = null;
    }
  };
  
  // 组件卸载时自动取消
  onUnmounted(() => {
    cancel();
  });
  
  return { execute, cancel };
};

// 使用示例
export default {
  setup() {
    const data = ref(null);
    const loading = ref(false);
    const { execute, cancel } = useCancellableAsync();
    
    const fetchData = async () => {
      loading.value = true;
      try {
        await execute(async (signal) => {
          const response = await fetch('/api/data', { signal });
          const result = await response.json();
          data.value = result;
        });
      } catch (error) {
        console.error('获取数据失败:', error);
      } finally {
        loading.value = false;
      }
    };
    
    return {
      data,
      loading,
      fetchData,
      cancel
    };
  }
};

5. 并发控制:管理多个异步操作

🔍 应用场景

需要同时管理多个异步操作,能够批量取消或单独取消特定操作。

✅ 推荐方案

javascript 复制代码
/**
 * 并发异步操作管理器
 * @description 管理多个并发的异步操作
 */
class ConcurrentAsyncManager {
  constructor() {
    this.operations = new Map();
    this.globalController = new AbortController();
  }
  
  /**
   * 添加异步操作
   * @param {string} id - 操作ID
   * @param {Function} asyncFn - 异步函数
   * @returns {Promise} 操作Promise
   */
  add = async (id, asyncFn) => {
    // 如果已存在相同ID的操作,先取消它
    if (this.operations.has(id)) {
      this.cancel(id);
    }
    
    const controller = new AbortController();
    this.operations.set(id, controller);
    
    try {
      // 监听全局取消信号
      this.globalController.signal.addEventListener('abort', () => {
        controller.abort();
      });
      
      const result = await asyncFn(controller.signal);
      this.operations.delete(id);
      return result;
    } catch (error) {
      this.operations.delete(id);
      if (error.name !== 'AbortError') {
        throw error;
      }
    }
  };
  
  /**
   * 取消特定操作
   * @param {string} id - 操作ID
   */
  cancel = (id) => {
    const controller = this.operations.get(id);
    if (controller) {
      controller.abort();
      this.operations.delete(id);
    }
  };
  
  /**
   * 取消所有操作
   */
  cancelAll = () => {
    this.globalController.abort();
    this.operations.clear();
    // 重新创建全局控制器
    this.globalController = new AbortController();
  };
  
  /**
   * 获取当前运行的操作数量
   */
  get activeCount() {
    return this.operations.size;
  }
  
  /**
   * 获取所有运行中的操作ID
   */
  get activeIds() {
    return Array.from(this.operations.keys());
  }
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const asyncManager = new ConcurrentAsyncManager();

// 添加多个并发操作
const fetchUserData = async () => {
  return asyncManager.add('user', async (signal) => {
    const response = await fetch('/api/user', { signal });
    return response.json();
  });
};

const fetchPostsData = async () => {
  return asyncManager.add('posts', async (signal) => {
    const response = await fetch('/api/posts', { signal });
    return response.json();
  });
};

const fetchCommentsData = async () => {
  return asyncManager.add('comments', async (signal) => {
    const response = await fetch('/api/comments', { signal });
    return response.json();
  });
};

// 同时发起多个请求
Promise.all([
  fetchUserData(),
  fetchPostsData(),
  fetchCommentsData()
]).then(([user, posts, comments]) => {
  console.log('所有数据获取完成:', { user, posts, comments });
}).catch(error => {
  console.error('数据获取失败:', error);
});

// 5秒后取消所有请求
setTimeout(() => {
  asyncManager.cancelAll();
}, 5000);

// 单独取消某个请求
setTimeout(() => {
  asyncManager.cancel('posts');
}, 2000);

6. 文件上传:可取消的上传操作

🔍 应用场景

大文件上传时,用户可能需要取消上传操作,特别是在网络较慢的情况下。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的文件上传器
 * @description 支持进度监控和取消操作的文件上传
 */
class CancellableFileUploader {
  constructor() {
    this.uploads = new Map();
  }
  
  /**
   * 上传文件
   * @param {File} file - 要上传的文件
   * @param {string} url - 上传地址
   * @param {Function} onProgress - 进度回调
   * @returns {Object} 包含promise和取消方法的对象
   */
  upload = (file, url, onProgress) => {
    const uploadId = Date.now() + Math.random();
    const controller = new AbortController();
    
    const formData = new FormData();
    formData.append('file', file);
    
    const uploadPromise = new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      
      // 监听上传进度
      xhr.upload.addEventListener('progress', (event) => {
        if (event.lengthComputable) {
          const progress = (event.loaded / event.total) * 100;
          if (onProgress) onProgress(progress);
        }
      });
      
      // 监听完成
      xhr.addEventListener('load', () => {
        this.uploads.delete(uploadId);
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(JSON.parse(xhr.responseText));
        } else {
          reject(new Error(`上传失败: ${xhr.status}`));
        }
      });
      
      // 监听错误
      xhr.addEventListener('error', () => {
        this.uploads.delete(uploadId);
        reject(new Error('上传失败'));
      });
      
      // 监听中止
      xhr.addEventListener('abort', () => {
        this.uploads.delete(uploadId);
        reject(new Error('上传已取消'));
      });
      
      // 监听取消信号
      controller.signal.addEventListener('abort', () => {
        xhr.abort();
      });
      
      xhr.open('POST', url);
      xhr.send(formData);
    });
    
    const uploadInfo = {
      id: uploadId,
      file,
      controller,
      promise: uploadPromise
    };
    
    this.uploads.set(uploadId, uploadInfo);
    
    return {
      id: uploadId,
      promise: uploadPromise,
      cancel: () => this.cancel(uploadId)
    };
  };
  
  /**
   * 取消上传
   * @param {string} uploadId - 上传ID
   */
  cancel = (uploadId) => {
    const upload = this.uploads.get(uploadId);
    if (upload) {
      upload.controller.abort();
      this.uploads.delete(uploadId);
    }
  };
  
  /**
   * 取消所有上传
   */
  cancelAll = () => {
    this.uploads.forEach((upload) => {
      upload.controller.abort();
    });
    this.uploads.clear();
  };
  
  /**
   * 获取当前上传数量
   */
  get activeUploads() {
    return this.uploads.size;
  }
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const uploader = new CancellableFileUploader();

// 文件选择处理
document.getElementById('file-input').addEventListener('change', (event) => {
  const files = Array.from(event.target.files);
  
  files.forEach((file) => {
    const { id, promise, cancel } = uploader.upload(
      file,
      '/api/upload',
      (progress) => {
        console.log(`${file.name} 上传进度: ${progress.toFixed(2)}%`);
        updateProgressBar(id, progress);
      }
    );
    
    // 创建取消按钮
    createCancelButton(id, cancel);
    
    // 处理上传结果
    promise
      .then((result) => {
        console.log(`${file.name} 上传成功:`, result);
        showSuccess(id, result);
      })
      .catch((error) => {
        console.error(`${file.name} 上传失败:`, error);
        showError(id, error.message);
      });
  });
});

// 取消所有上传按钮
document.getElementById('cancel-all').addEventListener('click', () => {
  uploader.cancelAll();
});

7. WebSocket连接:可控制的实时通信

🔍 应用场景

WebSocket连接的建立和断开控制,特别是在组件卸载或页面切换时。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的WebSocket管理器
 * @description 提供可控制的WebSocket连接管理
 */
class CancellableWebSocket {
  constructor(url, protocols) {
    this.url = url;
    this.protocols = protocols;
    this.ws = null;
    this.controller = new AbortController();
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000;
    this.listeners = new Map();
  }
  
  /**
   * 连接WebSocket
   * @returns {Promise} 连接Promise
   */
  connect = () => {
    return new Promise((resolve, reject) => {
      if (this.controller.signal.aborted) {
        reject(new Error('连接已被取消'));
        return;
      }
      
      this.ws = new WebSocket(this.url, this.protocols);
      
      const onOpen = () => {
        this.reconnectAttempts = 0;
        this.emit('open');
        resolve();
      };
      
      const onMessage = (event) => {
        this.emit('message', event.data);
      };
      
      const onError = (error) => {
        this.emit('error', error);
        reject(error);
      };
      
      const onClose = (event) => {
        this.emit('close', event);
        
        // 如果不是主动关闭且未被取消,尝试重连
        if (!event.wasClean && !this.controller.signal.aborted && 
            this.reconnectAttempts < this.maxReconnectAttempts) {
          this.reconnectAttempts++;
          setTimeout(() => {
            this.connect().catch(() => {
              // 重连失败,继续尝试或放弃
            });
          }, this.reconnectDelay * this.reconnectAttempts);
        }
      };
      
      // 监听取消信号
      this.controller.signal.addEventListener('abort', () => {
        if (this.ws) {
          this.ws.close();
        }
        reject(new Error('连接已被取消'));
      });
      
      this.ws.addEventListener('open', onOpen);
      this.ws.addEventListener('message', onMessage);
      this.ws.addEventListener('error', onError);
      this.ws.addEventListener('close', onClose);
    });
  };
  
  /**
   * 发送消息
   * @param {string} data - 要发送的数据
   */
  send = (data) => {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      throw new Error('WebSocket连接未建立');
    }
  };
  
  /**
   * 关闭连接
   */
  close = () => {
    this.controller.abort();
    if (this.ws) {
      this.ws.close();
    }
  };
  
  /**
   * 添加事件监听器
   * @param {string} event - 事件名
   * @param {Function} listener - 监听器函数
   */
  on = (event, listener) => {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(listener);
  };
  
  /**
   * 移除事件监听器
   * @param {string} event - 事件名
   * @param {Function} listener - 监听器函数
   */
  off = (event, listener) => {
    const eventListeners = this.listeners.get(event);
    if (eventListeners) {
      const index = eventListeners.indexOf(listener);
      if (index > -1) {
        eventListeners.splice(index, 1);
      }
    }
  };
  
  /**
   * 触发事件
   * @param {string} event - 事件名
   * @param {*} data - 事件数据
   */
  emit = (event, data) => {
    const eventListeners = this.listeners.get(event);
    if (eventListeners) {
      eventListeners.forEach(listener => listener(data));
    }
  };
  
  /**
   * 获取连接状态
   */
  get readyState() {
    return this.ws ? this.ws.readyState : WebSocket.CLOSED;
  }
  
  /**
   * 检查是否已连接
   */
  get isConnected() {
    return this.ws && this.ws.readyState === WebSocket.OPEN;
  }
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const wsManager = new CancellableWebSocket('ws://localhost:8080');

// 添加事件监听
wsManager.on('open', () => {
  console.log('WebSocket连接已建立');
});

wsManager.on('message', (data) => {
  console.log('收到消息:', data);
  const message = JSON.parse(data);
  handleMessage(message);
});

wsManager.on('error', (error) => {
  console.error('WebSocket错误:', error);
});

wsManager.on('close', (event) => {
  console.log('WebSocket连接已关闭:', event);
});

// 建立连接
wsManager.connect()
  .then(() => {
    console.log('连接成功');
    // 发送心跳
    setInterval(() => {
      if (wsManager.isConnected) {
        wsManager.send(JSON.stringify({ type: 'ping' }));
      }
    }, 30000);
  })
  .catch((error) => {
    console.error('连接失败:', error);
  });

// 页面卸载时关闭连接
window.addEventListener('beforeunload', () => {
  wsManager.close();
});

// 发送消息示例
const sendMessage = (message) => {
  try {
    wsManager.send(JSON.stringify(message));
  } catch (error) {
    console.error('发送消息失败:', error);
  }
};

8. 图片懒加载:可取消的图片加载

🔍 应用场景

在图片懒加载场景中,当图片离开视口或组件卸载时,取消正在加载的图片请求。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的图片懒加载管理器
 * @description 支持取消的图片懒加载实现
 */
class CancellableLazyLoader {
  constructor() {
    this.loadingImages = new Map();
    this.observer = null;
    this.globalController = new AbortController();
  }
  
  /**
   * 初始化懒加载
   * @param {string} selector - 图片选择器
   * @param {Object} options - 配置选项
   */
  init = (selector = 'img[data-src]', options = {}) => {
    const defaultOptions = {
      rootMargin: '50px',
      threshold: 0.1,
      ...options
    };
    
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          this.loadImage(entry.target);
        } else {
          // 图片离开视口时取消加载
          this.cancelImageLoad(entry.target);
        }
      });
    }, defaultOptions);
    
    // 观察所有目标图片
    document.querySelectorAll(selector).forEach((img) => {
      this.observer.observe(img);
    });
  };
  
  /**
   * 加载单个图片
   * @param {HTMLImageElement} img - 图片元素
   */
  loadImage = async (img) => {
    const src = img.dataset.src;
    if (!src || this.loadingImages.has(img)) return;
    
    const controller = new AbortController();
    this.loadingImages.set(img, controller);
    
    try {
      // 监听全局取消信号
      this.globalController.signal.addEventListener('abort', () => {
        controller.abort();
      });
      
      // 创建新的图片对象进行预加载
      const imageLoader = new Promise((resolve, reject) => {
        const tempImg = new Image();
        
        const cleanup = () => {
          tempImg.onload = null;
          tempImg.onerror = null;
        };
        
        tempImg.onload = () => {
          cleanup();
          resolve(tempImg.src);
        };
        
        tempImg.onerror = () => {
          cleanup();
          reject(new Error('图片加载失败'));
        };
        
        // 监听取消信号
        controller.signal.addEventListener('abort', () => {
          cleanup();
          reject(new Error('图片加载已取消'));
        });
        
        tempImg.src = src;
      });
      
      const loadedSrc = await imageLoader;
      
      // 如果没有被取消,更新图片源
      if (!controller.signal.aborted) {
        img.src = loadedSrc;
        img.classList.add('loaded');
        this.observer.unobserve(img);
      }
    } catch (error) {
      if (error.message !== '图片加载已取消') {
        console.error('图片加载失败:', error);
        img.classList.add('error');
      }
    } finally {
      this.loadingImages.delete(img);
    }
  };
  
  /**
   * 取消图片加载
   * @param {HTMLImageElement} img - 图片元素
   */
  cancelImageLoad = (img) => {
    const controller = this.loadingImages.get(img);
    if (controller) {
      controller.abort();
      this.loadingImages.delete(img);
    }
  };
  
  /**
   * 取消所有图片加载
   */
  cancelAll = () => {
    this.globalController.abort();
    this.loadingImages.clear();
    // 重新创建全局控制器
    this.globalController = new AbortController();
  };
  
  /**
   * 销毁懒加载器
   */
  destroy = () => {
    this.cancelAll();
    if (this.observer) {
      this.observer.disconnect();
    }
  };
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const lazyLoader = new CancellableLazyLoader();

// 初始化懒加载
lazyLoader.init('img[data-src]', {
  rootMargin: '100px',
  threshold: 0.1
});

// 页面卸载时清理
window.addEventListener('beforeunload', () => {
  lazyLoader.destroy();
});

// 动态添加图片时重新观察
const addNewImage = (src, container) => {
  const img = document.createElement('img');
  img.dataset.src = src;
  img.classList.add('lazy-image');
  container.appendChild(img);
  
  // 观察新图片
  lazyLoader.observer.observe(img);
};

9. 定时任务:可取消的定时器和轮询

🔍 应用场景

管理定时任务、轮询请求等需要在特定条件下取消的定时操作。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的定时任务管理器
 * @description 管理各种定时任务和轮询操作
 */
class CancellableScheduler {
  constructor() {
    this.tasks = new Map();
    this.globalController = new AbortController();
  }
  
  /**
   * 创建可取消的延时任务
   * @param {Function} callback - 回调函数
   * @param {number} delay - 延时时间
   * @param {string} id - 任务ID
   * @returns {Promise} 任务Promise
   */
  delay = (callback, delay, id = Date.now()) => {
    const controller = new AbortController();
    this.tasks.set(id, { type: 'delay', controller });
    
    return new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        if (!controller.signal.aborted) {
          try {
            const result = callback();
            this.tasks.delete(id);
            resolve(result);
          } catch (error) {
            this.tasks.delete(id);
            reject(error);
          }
        }
      }, delay);
      
      // 监听取消信号
      const abortHandler = () => {
        clearTimeout(timeoutId);
        this.tasks.delete(id);
        reject(new Error('任务已取消'));
      };
      
      controller.signal.addEventListener('abort', abortHandler);
      this.globalController.signal.addEventListener('abort', abortHandler);
    });
  };
  
  /**
   * 创建可取消的轮询任务
   * @param {Function} callback - 回调函数
   * @param {number} interval - 轮询间隔
   * @param {string} id - 任务ID
   * @returns {Object} 包含停止方法的对象
   */
  poll = (callback, interval, id = Date.now()) => {
    const controller = new AbortController();
    this.tasks.set(id, { type: 'poll', controller });
    
    let timeoutId;
    
    const runPoll = async () => {
      if (controller.signal.aborted) return;
      
      try {
        await callback();
      } catch (error) {
        console.error('轮询任务执行失败:', error);
      }
      
      if (!controller.signal.aborted) {
        timeoutId = setTimeout(runPoll, interval);
      }
    };
    
    // 监听取消信号
    const abortHandler = () => {
      if (timeoutId) {
        clearTimeout(timeoutId);
      }
      this.tasks.delete(id);
    };
    
    controller.signal.addEventListener('abort', abortHandler);
    this.globalController.signal.addEventListener('abort', abortHandler);
    
    // 立即开始第一次轮询
    runPoll();
    
    return {
      id,
      stop: () => controller.abort()
    };
  };
  
  /**
   * 创建可取消的重试任务
   * @param {Function} asyncFn - 异步函数
   * @param {Object} options - 重试配置
   * @param {string} id - 任务ID
   * @returns {Promise} 任务Promise
   */
  retry = async (asyncFn, options = {}, id = Date.now()) => {
    const {
      maxAttempts = 3,
      delay = 1000,
      backoff = 2
    } = options;
    
    const controller = new AbortController();
    this.tasks.set(id, { type: 'retry', controller });
    
    let attempt = 0;
    
    const executeWithRetry = async () => {
      while (attempt < maxAttempts && !controller.signal.aborted) {
        try {
          const result = await asyncFn();
          this.tasks.delete(id);
          return result;
        } catch (error) {
          attempt++;
          
          if (attempt >= maxAttempts || controller.signal.aborted) {
            this.tasks.delete(id);
            throw error;
          }
          
          // 等待重试延时
          await new Promise((resolve, reject) => {
            const retryDelay = delay * Math.pow(backoff, attempt - 1);
            const timeoutId = setTimeout(resolve, retryDelay);
            
            controller.signal.addEventListener('abort', () => {
              clearTimeout(timeoutId);
              reject(new Error('重试已取消'));
            });
          });
        }
      }
    };
    
    // 监听全局取消信号
    this.globalController.signal.addEventListener('abort', () => {
      controller.abort();
    });
    
    return executeWithRetry();
  };
  
  /**
   * 取消特定任务
   * @param {string} id - 任务ID
   */
  cancel = (id) => {
    const task = this.tasks.get(id);
    if (task) {
      task.controller.abort();
    }
  };
  
  /**
   * 取消所有任务
   */
  cancelAll = () => {
    this.globalController.abort();
    this.tasks.clear();
    // 重新创建全局控制器
    this.globalController = new AbortController();
  };
  
  /**
   * 获取活跃任务数量
   */
  get activeTaskCount() {
    return this.tasks.size;
  }
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const scheduler = new CancellableScheduler();

// 延时任务
scheduler.delay(() => {
  console.log('延时任务执行');
  return '任务完成';
}, 2000, 'delay-task-1')
.then(result => console.log(result))
.catch(error => console.log(error.message));

// 轮询任务
const pollTask = scheduler.poll(async () => {
  const response = await fetch('/api/status');
  const data = await response.json();
  console.log('状态检查:', data);
  
  // 满足条件时停止轮询
  if (data.status === 'completed') {
    pollTask.stop();
  }
}, 5000, 'status-poll');

// 重试任务
scheduler.retry(async () => {
  const response = await fetch('/api/unreliable-endpoint');
  if (!response.ok) {
    throw new Error('请求失败');
  }
  return response.json();
}, {
  maxAttempts: 5,
  delay: 1000,
  backoff: 2
}, 'retry-task')
.then(data => console.log('重试成功:', data))
.catch(error => console.log('重试失败:', error));

// 页面卸载时取消所有任务
window.addEventListener('beforeunload', () => {
  scheduler.cancelAll();
});

10. 数据流处理:可取消的流式数据处理

🔍 应用场景

处理大量数据流、实时数据处理、流式API响应等需要中途取消的场景。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的数据流处理器
 * @description 处理流式数据,支持取消操作
 */
class CancellableStreamProcessor {
  constructor() {
    this.processors = new Map();
  }
  
  /**
   * 处理流式响应
   * @param {string} url - 请求地址
   * @param {Object} options - 请求选项
   * @param {Function} onChunk - 数据块处理函数
   * @param {string} id - 处理器ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  processStream = (url, options = {}, onChunk, id = Date.now()) => {
    const controller = new AbortController();
    this.processors.set(id, controller);
    
    const streamPromise = this.handleStreamResponse(url, options, onChunk, controller.signal);
    
    return {
      id,
      promise: streamPromise,
      cancel: () => this.cancel(id)
    };
  };
  
  /**
   * 处理流式响应的核心逻辑
   * @param {string} url - 请求地址
   * @param {Object} options - 请求选项
   * @param {Function} onChunk - 数据块处理函数
   * @param {AbortSignal} signal - 取消信号
   */
  handleStreamResponse = async (url, options, onChunk, signal) => {
    try {
      const response = await fetch(url, {
        ...options,
        signal
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      
      try {
        while (true) {
          if (signal.aborted) {
            throw new Error('流处理已取消');
          }
          
          const { done, value } = await reader.read();
          
          if (done) break;
          
          const chunk = decoder.decode(value, { stream: true });
          
          // 处理数据块
          if (onChunk) {
            await onChunk(chunk);
          }
        }
      } finally {
        reader.releaseLock();
      }
    } catch (error) {
      if (error.name === 'AbortError' || error.message === '流处理已取消') {
        console.log('流处理已取消');
      } else {
        throw error;
      }
    }
  };
  
  /**
   * 处理JSON流数据
   * @param {string} url - 请求地址
   * @param {Function} onObject - 对象处理函数
   * @param {string} id - 处理器ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  processJSONStream = (url, onObject, id = Date.now()) => {
    let buffer = '';
    
    return this.processStream(url, {}, (chunk) => {
      buffer += chunk;
      const lines = buffer.split('\n');
      buffer = lines.pop(); // 保留最后一个不完整的行
      
      lines.forEach(line => {
        if (line.trim()) {
          try {
            const obj = JSON.parse(line);
            onObject(obj);
          } catch (error) {
            console.error('JSON解析失败:', error, line);
          }
        }
      });
    }, id);
  };
  
  /**
   * 批量处理数据
   * @param {Array} data - 数据数组
   * @param {Function} processor - 处理函数
   * @param {Object} options - 配置选项
   * @param {string} id - 处理器ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  processBatch = (data, processor, options = {}, id = Date.now()) => {
    const {
      batchSize = 100,
      delay = 10
    } = options;
    
    const controller = new AbortController();
    this.processors.set(id, controller);
    
    const batchPromise = this.handleBatchProcessing(data, processor, batchSize, delay, controller.signal);
    
    return {
      id,
      promise: batchPromise,
      cancel: () => this.cancel(id)
    };
  };
  
  /**
   * 批量处理的核心逻辑
   * @param {Array} data - 数据数组
   * @param {Function} processor - 处理函数
   * @param {number} batchSize - 批次大小
   * @param {number} delay - 批次间延时
   * @param {AbortSignal} signal - 取消信号
   */
  handleBatchProcessing = async (data, processor, batchSize, delay, signal) => {
    const results = [];
    
    for (let i = 0; i < data.length; i += batchSize) {
      if (signal.aborted) {
        throw new Error('批量处理已取消');
      }
      
      const batch = data.slice(i, i + batchSize);
      const batchResults = await Promise.all(
        batch.map(item => processor(item))
      );
      
      results.push(...batchResults);
      
      // 批次间延时
      if (i + batchSize < data.length && delay > 0) {
        await new Promise(resolve => {
          const timeoutId = setTimeout(resolve, delay);
          signal.addEventListener('abort', () => {
            clearTimeout(timeoutId);
          });
        });
      }
    }
    
    return results;
  };
  
  /**
   * 取消特定处理器
   * @param {string} id - 处理器ID
   */
  cancel = (id) => {
    const controller = this.processors.get(id);
    if (controller) {
      controller.abort();
      this.processors.delete(id);
    }
  };
  
  /**
   * 取消所有处理器
   */
  cancelAll = () => {
    this.processors.forEach(controller => controller.abort());
    this.processors.clear();
  };
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const streamProcessor = new CancellableStreamProcessor();

// 处理流式API响应
const { promise: streamPromise, cancel: cancelStream } = streamProcessor.processStream(
  '/api/stream-data',
  {},
  (chunk) => {
    console.log('收到数据块:', chunk);
    // 处理数据块
    updateUI(chunk);
  },
  'main-stream'
);

streamPromise
  .then(() => console.log('流处理完成'))
  .catch(error => console.error('流处理失败:', error));

// 处理JSON流
const { promise: jsonPromise, cancel: cancelJSON } = streamProcessor.processJSONStream(
  '/api/json-stream',
  (obj) => {
    console.log('收到对象:', obj);
    processDataObject(obj);
  },
  'json-stream'
);

// 批量处理大量数据
const largeDataset = Array.from({ length: 10000 }, (_, i) => ({ id: i, value: Math.random() }));

const { promise: batchPromise, cancel: cancelBatch } = streamProcessor.processBatch(
  largeDataset,
  async (item) => {
    // 模拟异步处理
    await new Promise(resolve => setTimeout(resolve, 1));
    return { ...item, processed: true };
  },
  {
    batchSize: 50,
    delay: 10
  },
  'batch-process'
);

batchPromise
  .then(results => console.log('批量处理完成:', results.length))
  .catch(error => console.error('批量处理失败:', error));

// 用户操作取消
document.getElementById('cancel-stream').addEventListener('click', () => {
  cancelStream();
});

document.getElementById('cancel-all').addEventListener('click', () => {
  streamProcessor.cancelAll();
});

11. 动画控制:可取消的动画和过渡效果

🔍 应用场景

管理CSS动画、JavaScript动画、过渡效果等,在组件卸载或状态变化时取消动画。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的动画控制器
 * @description 管理各种动画效果,支持取消操作
 */
class CancellableAnimationController {
  constructor() {
    this.animations = new Map();
    this.globalController = new AbortController();
  }
  
  /**
   * 创建可取消的requestAnimationFrame动画
   * @param {Function} animationFn - 动画函数
   * @param {Object} options - 动画选项
   * @param {string} id - 动画ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  animate = (animationFn, options = {}, id = Date.now()) => {
    const {
      duration = 1000,
      easing = (t) => t // 线性缓动
    } = options;
    
    const controller = new AbortController();
    this.animations.set(id, { type: 'raf', controller });
    
    const animationPromise = new Promise((resolve, reject) => {
      const startTime = performance.now();
      let rafId;
      
      const frame = (currentTime) => {
        if (controller.signal.aborted) {
          reject(new Error('动画已取消'));
          return;
        }
        
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const easedProgress = easing(progress);
        
        // 执行动画函数
        animationFn(easedProgress, elapsed);
        
        if (progress < 1) {
          rafId = requestAnimationFrame(frame);
        } else {
          this.animations.delete(id);
          resolve();
        }
      };
      
      // 监听取消信号
      controller.signal.addEventListener('abort', () => {
        if (rafId) {
          cancelAnimationFrame(rafId);
        }
        this.animations.delete(id);
      });
      
      // 监听全局取消信号
      this.globalController.signal.addEventListener('abort', () => {
        controller.abort();
      });
      
      rafId = requestAnimationFrame(frame);
    });
    
    return {
      id,
      promise: animationPromise,
      cancel: () => this.cancel(id)
    };
  };
  
  /**
   * 创建可取消的CSS过渡动画
   * @param {HTMLElement} element - 目标元素
   * @param {Object} styles - 目标样式
   * @param {Object} options - 动画选项
   * @param {string} id - 动画ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  transition = (element, styles, options = {}, id = Date.now()) => {
    const {
      duration = 300,
      easing = 'ease',
      delay = 0
    } = options;
    
    const controller = new AbortController();
    this.animations.set(id, { type: 'transition', controller, element });
    
    const transitionPromise = new Promise((resolve, reject) => {
      // 保存原始样式
      const originalStyles = {};
      Object.keys(styles).forEach(prop => {
        originalStyles[prop] = element.style[prop];
      });
      
      // 设置过渡属性
      element.style.transition = `all ${duration}ms ${easing} ${delay}ms`;
      
      // 应用新样式
      Object.assign(element.style, styles);
      
      // 监听过渡结束
      const handleTransitionEnd = (event) => {
        if (event.target === element) {
          cleanup();
          this.animations.delete(id);
          resolve();
        }
      };
      
      // 监听取消信号
      const handleAbort = () => {
        cleanup();
        // 恢复原始样式
        Object.assign(element.style, originalStyles);
        element.style.transition = '';
        this.animations.delete(id);
        reject(new Error('过渡动画已取消'));
      };
      
      const cleanup = () => {
        element.removeEventListener('transitionend', handleTransitionEnd);
        controller.signal.removeEventListener('abort', handleAbort);
      };
      
      element.addEventListener('transitionend', handleTransitionEnd);
      controller.signal.addEventListener('abort', handleAbort);
      
      // 监听全局取消信号
      this.globalController.signal.addEventListener('abort', () => {
        controller.abort();
      });
    });
    
    return {
      id,
      promise: transitionPromise,
      cancel: () => this.cancel(id)
    };
  };
  
  /**
   * 创建可取消的关键帧动画
   * @param {HTMLElement} element - 目标元素
   * @param {Array} keyframes - 关键帧数组
   * @param {Object} options - 动画选项
   * @param {string} id - 动画ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  keyframes = (element, keyframes, options = {}, id = Date.now()) => {
    const controller = new AbortController();
    this.animations.set(id, { type: 'keyframes', controller, element });
    
    const keyframePromise = new Promise((resolve, reject) => {
      // 创建Web Animations API动画
      const animation = element.animate(keyframes, options);
      
      // 监听动画完成
      animation.addEventListener('finish', () => {
        this.animations.delete(id);
        resolve();
      });
      
      // 监听取消信号
      const handleAbort = () => {
        animation.cancel();
        this.animations.delete(id);
        reject(new Error('关键帧动画已取消'));
      };
      
      controller.signal.addEventListener('abort', handleAbort);
      
      // 监听全局取消信号
      this.globalController.signal.addEventListener('abort', () => {
        controller.abort();
      });
    });
    
    return {
      id,
      promise: keyframePromise,
      cancel: () => this.cancel(id)
    };
  };
  
  /**
   * 取消特定动画
   * @param {string} id - 动画ID
   */
  cancel = (id) => {
    const animation = this.animations.get(id);
    if (animation) {
      animation.controller.abort();
    }
  };
  
  /**
   * 取消所有动画
   */
  cancelAll = () => {
    this.globalController.abort();
    this.animations.clear();
    // 重新创建全局控制器
    this.globalController = new AbortController();
  };
  
  /**
   * 获取活跃动画数量
   */
  get activeAnimationCount() {
    return this.animations.size;
  }
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const animationController = new CancellableAnimationController();

// requestAnimationFrame动画
const { promise: rafPromise, cancel: cancelRaf } = animationController.animate(
  (progress, elapsed) => {
    const element = document.getElementById('animated-box');
    const x = progress * 300; // 移动300px
    element.style.transform = `translateX(${x}px)`;
  },
  {
    duration: 2000,
    easing: (t) => t * t // 二次缓动
  },
  'box-animation'
);

rafPromise
  .then(() => console.log('动画完成'))
  .catch(error => console.log(error.message));

// CSS过渡动画
const element = document.getElementById('transition-box');
const { promise: transitionPromise, cancel: cancelTransition } = animationController.transition(
  element,
  {
    opacity: '0',
    transform: 'scale(0.5)'
  },
  {
    duration: 500,
    easing: 'ease-out'
  },
  'fade-out'
);

// 关键帧动画
const { promise: keyframePromise, cancel: cancelKeyframe } = animationController.keyframes(
  element,
  [
    { transform: 'rotate(0deg)' },
    { transform: 'rotate(180deg)' },
    { transform: 'rotate(360deg)' }
  ],
  {
    duration: 1000,
    iterations: Infinity
  },
  'rotation'
);

// 用户交互取消动画
document.getElementById('cancel-animations').addEventListener('click', () => {
  animationController.cancelAll();
});

// 组件卸载时取消动画
window.addEventListener('beforeunload', () => {
  animationController.cancelAll();
});

12. 缓存管理:可取消的缓存操作

🔍 应用场景

管理缓存的读取、写入、清理等操作,在数据过期或组件卸载时取消缓存操作。

✅ 推荐方案

javascript 复制代码
/**
 * 可取消的缓存管理器
 * @description 提供可取消的缓存操作功能
 */
class CancellableCacheManager {
  constructor(options = {}) {
    this.cache = new Map();
    this.operations = new Map();
    this.globalController = new AbortController();
    this.defaultTTL = options.defaultTTL || 300000; // 5分钟默认TTL
    this.maxSize = options.maxSize || 1000;
  }
  
  /**
   * 获取缓存数据
   * @param {string} key - 缓存键
   * @param {Function} fetcher - 数据获取函数
   * @param {Object} options - 选项
   * @returns {Object} 包含promise和取消方法的对象
   */
  get = (key, fetcher, options = {}) => {
    const {
      ttl = this.defaultTTL,
      forceRefresh = false
    } = options;
    
    const operationId = `get-${key}-${Date.now()}`;
    const controller = new AbortController();
    this.operations.set(operationId, controller);
    
    const getPromise = this.handleGet(key, fetcher, ttl, forceRefresh, controller.signal);
    
    return {
      id: operationId,
      promise: getPromise,
      cancel: () => this.cancelOperation(operationId)
    };
  };
  
  /**
   * 处理缓存获取的核心逻辑
   * @param {string} key - 缓存键
   * @param {Function} fetcher - 数据获取函数
   * @param {number} ttl - 生存时间
   * @param {boolean} forceRefresh - 强制刷新
   * @param {AbortSignal} signal - 取消信号
   */
  handleGet = async (key, fetcher, ttl, forceRefresh, signal) => {
    try {
      // 检查是否已取消
      if (signal.aborted) {
        throw new Error('缓存操作已取消');
      }
      
      // 检查缓存是否存在且未过期
      if (!forceRefresh && this.cache.has(key)) {
        const cached = this.cache.get(key);
        if (Date.now() < cached.expiry) {
          return cached.data;
        } else {
          // 缓存已过期,删除
          this.cache.delete(key);
        }
      }
      
      // 获取新数据
      const data = await fetcher(signal);
      
      // 检查是否在获取过程中被取消
      if (signal.aborted) {
        throw new Error('缓存操作已取消');
      }
      
      // 存储到缓存
      this.set(key, data, ttl);
      
      return data;
    } catch (error) {
      if (error.name === 'AbortError' || error.message === '缓存操作已取消') {
        throw new Error('缓存操作已取消');
      }
      throw error;
    }
  };
  
  /**
   * 设置缓存数据
   * @param {string} key - 缓存键
   * @param {*} data - 数据
   * @param {number} ttl - 生存时间
   */
  set = (key, data, ttl = this.defaultTTL) => {
    // 检查缓存大小限制
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      // 删除最旧的缓存项
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    
    this.cache.set(key, {
      data,
      expiry: Date.now() + ttl,
      createdAt: Date.now()
    });
  };
  
  /**
   * 批量获取缓存数据
   * @param {Array} keys - 缓存键数组
   * @param {Function} fetcher - 数据获取函数
   * @param {Object} options - 选项
   * @returns {Object} 包含promise和取消方法的对象
   */
  getBatch = (keys, fetcher, options = {}) => {
    const operationId = `batch-${Date.now()}`;
    const controller = new AbortController();
    this.operations.set(operationId, controller);
    
    const batchPromise = this.handleBatchGet(keys, fetcher, options, controller.signal);
    
    return {
      id: operationId,
      promise: batchPromise,
      cancel: () => this.cancelOperation(operationId)
    };
  };
  
  /**
   * 处理批量获取的核心逻辑
   * @param {Array} keys - 缓存键数组
   * @param {Function} fetcher - 数据获取函数
   * @param {Object} options - 选项
   * @param {AbortSignal} signal - 取消信号
   */
  handleBatchGet = async (keys, fetcher, options, signal) => {
    const results = {};
    const missingKeys = [];
    
    // 检查缓存中已存在的数据
    keys.forEach(key => {
      if (this.cache.has(key)) {
        const cached = this.cache.get(key);
        if (Date.now() < cached.expiry) {
          results[key] = cached.data;
        } else {
          this.cache.delete(key);
          missingKeys.push(key);
        }
      } else {
        missingKeys.push(key);
      }
    });
    
    // 获取缺失的数据
    if (missingKeys.length > 0 && !signal.aborted) {
      const fetchedData = await fetcher(missingKeys, signal);
      
      if (!signal.aborted) {
        // 存储获取的数据
        Object.entries(fetchedData).forEach(([key, data]) => {
          this.set(key, data, options.ttl);
          results[key] = data;
        });
      }
    }
    
    if (signal.aborted) {
      throw new Error('批量缓存操作已取消');
    }
    
    return results;
  };
  
  /**
   * 清理过期缓存
   * @param {string} id - 操作ID
   * @returns {Object} 包含promise和取消方法的对象
   */
  cleanup = (id = Date.now()) => {
    const controller = new AbortController();
    this.operations.set(id, controller);
    
    const cleanupPromise = new Promise((resolve, reject) => {
      const now = Date.now();
      const expiredKeys = [];
      
      // 查找过期的键
      for (const [key, cached] of this.cache.entries()) {
        if (controller.signal.aborted) {
          reject(new Error('清理操作已取消'));
          return;
        }
        
        if (now >= cached.expiry) {
          expiredKeys.push(key);
        }
      }
      
      // 删除过期的缓存
      expiredKeys.forEach(key => {
        if (!controller.signal.aborted) {
          this.cache.delete(key);
        }
      });
      
      this.operations.delete(id);
      
      if (controller.signal.aborted) {
        reject(new Error('清理操作已取消'));
      } else {
        resolve(expiredKeys.length);
      }
    });
    
    // 监听全局取消信号
    this.globalController.signal.addEventListener('abort', () => {
      controller.abort();
    });
    
    return {
      id,
      promise: cleanupPromise,
      cancel: () => this.cancelOperation(id)
    };
  };
  
  /**
   * 取消特定操作
   * @param {string} operationId - 操作ID
   */
  cancelOperation = (operationId) => {
    const controller = this.operations.get(operationId);
    if (controller) {
      controller.abort();
      this.operations.delete(operationId);
    }
  };
  
  /**
   * 取消所有操作
   */
  cancelAll = () => {
    this.globalController.abort();
    this.operations.clear();
    // 重新创建全局控制器
    this.globalController = new AbortController();
  };
  
  /**
   * 清空所有缓存
   */
  clear = () => {
    this.cache.clear();
  };
  
  /**
   * 获取缓存统计信息
   */
  getStats = () => {
    const now = Date.now();
    let expiredCount = 0;
    
    for (const cached of this.cache.values()) {
      if (now >= cached.expiry) {
        expiredCount++;
      }
    }
    
    return {
      total: this.cache.size,
      expired: expiredCount,
      active: this.cache.size - expiredCount,
      activeOperations: this.operations.size
    };
  };
}

🎯 实际应用

javascript 复制代码
// 实际项目中的应用
const cacheManager = new CancellableCacheManager({
  defaultTTL: 300000, // 5分钟
  maxSize: 500
});

// 获取用户数据
const { promise: userPromise, cancel: cancelUser } = cacheManager.get(
  'user-123',
  async (signal) => {
    const response = await fetch('/api/user/123', { signal });
    return response.json();
  },
  { ttl: 600000 } // 10分钟TTL
);

userPromise
  .then(userData => console.log('用户数据:', userData))
  .catch(error => console.log('获取失败:', error.message));

// 批量获取数据
const { promise: batchPromise, cancel: cancelBatch } = cacheManager.getBatch(
  ['post-1', 'post-2', 'post-3'],
  async (missingKeys, signal) => {
    const response = await fetch('/api/posts/batch', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids: missingKeys }),
      signal
    });
    return response.json();
  }
);

// 定期清理过期缓存
setInterval(() => {
  const { promise: cleanupPromise } = cacheManager.cleanup();
  cleanupPromise.then(count => {
    console.log(`清理了 ${count} 个过期缓存项`);
  });
}, 60000); // 每分钟清理一次

// 页面卸载时取消所有操作
window.addEventListener('beforeunload', () => {
  cacheManager.cancelAll();
});

// 获取缓存统计
const stats = cacheManager.getStats();
console.log('缓存统计:', stats);

📊 技巧对比总结

应用场景 使用方式 主要优势 注意事项
基础请求取消 controller.signal传递给fetch 原生支持,无需额外依赖 需要正确处理AbortError
搜索防抖 结合防抖和请求取消 避免无效请求,提升性能 注意清理定时器和控制器
超时控制 setTimeout + abort 自动超时管理 记得清理定时器
组件生命周期 Hook/Composition API封装 自动管理,防止内存泄漏 确保在正确时机取消
并发控制 Map管理多个控制器 灵活的批量和单独控制 注意内存管理和状态同步
文件上传 XMLHttpRequest + AbortController 支持进度和取消 处理好各种状态和错误
WebSocket 自定义封装类 完整的连接生命周期管理 考虑重连机制和事件清理
图片懒加载 IntersectionObserver + AbortController 视口变化时自动取消加载 注意图片对象的内存管理
定时任务 统一的任务管理器 集中管理各种定时操作 合理设置任务优先级和清理
数据流处理 ReadableStream + AbortController 及时释放流资源 正确处理流的生命周期
动画控制 多种动画API统一管理 避免动画冲突和性能问题 注意动画状态的恢复
缓存管理 异步操作的统一取消 避免过期操作影响缓存 合理设置缓存策略和TTL

🎯 实战应用建议

最佳实践

  1. 统一错误处理始终检查error.name === 'AbortError'来区分取消和其他错误
  2. 资源清理:在组件卸载或页面离开时主动取消未完成的操作
  3. 用户体验:为长时间操作提供取消按钮,提升用户控制感
  4. 性能优化:在快速操作场景下使用取消机制避免资源浪费
  5. 状态管理:合理管理控制器的生命周期,避免内存泄漏

性能考虑

  • 内存管理:及时清理不再需要的AbortController实例
  • 网络优化:避免重复请求,合理使用取消机制
  • 用户体验:提供清晰的加载状态和取消反馈

兼容性注意

  • 浏览器支持:AbortController在现代浏览器中支持良好,IE不支持
  • Polyfill:可以使用abort-controller polyfill来支持旧浏览器
  • Node.js:Node.js 15+原生支持,早期版本需要polyfill

💡 总结

这12个AbortController应用场景在日常开发中能够显著提升异步操作的管理质量,掌握它们能让你的代码:

🔥 核心场景(7个)

  1. 基础请求取消:告别手动flag管理,拥抱原生取消机制
  2. 搜索防抖优化:智能取消过期请求,提升搜索体验
  3. 超时自动控制:优雅处理慢请求,避免用户长时间等待
  4. 组件生命周期管理:防止内存泄漏,确保状态一致性
  5. 并发操作控制:灵活管理多个异步任务
  6. 文件上传管理:提供完整的上传控制体验
  7. WebSocket连接控制:可靠的实时通信管理

⚡ 进阶场景(5个)

  1. 图片懒加载控制:智能管理图片加载,优化页面性能
  2. 定时任务管理:统一管理延时、轮询、重试等定时操作
  3. 数据流处理:高效处理大数据流和实时数据
  4. 动画控制:统一管理各种动画效果,避免冲突
  5. 缓存管理:智能的缓存操作控制,提升数据管理效率

希望这些技巧能帮助你在前端开发中更优雅地处理异步操作,写出更健壮的代码!


🔗 相关资源


💡 今日收获:掌握了7个AbortController的实际应用场景,这些知识点在异步操作管理中非常实用。

如果这篇文章对你有帮助,欢迎点赞、收藏和分享!有任何问题也欢迎在评论区讨论。 🚀

相关推荐
JohnYan3 小时前
工作笔记 - VSCode ssh远程开发
javascript·ssh·visual studio code
code_YuJun3 小时前
前端脚手架开发流程
前端
golang学习记4 小时前
从0死磕全栈之使用 VS Code 调试 Next.js 应用完整指南
前端
shayudiandian4 小时前
JavaScript性能优化实战
开发语言·javascript·性能优化
Mintopia4 小时前
🧩 隐私计算技术在 Web AIGC 数据处理中的应用实践
前端·javascript·aigc
尘世中一位迷途小书童4 小时前
代码质量保障:ESLint + Prettier + Stylelint 三剑客完美配置
前端·架构
Mintopia4 小时前
🧭 Next.js 架构与运维:当现代前端拥有了“分布式的灵魂”
前端·javascript·全栈
尘世中一位迷途小书童4 小时前
从零搭建:pnpm + Turborepo 项目架构实战(含完整代码)
前端·架构
JarvanMo4 小时前
Flutter 中的 ClipRRect | 每日 Flutter 组件
前端