前端Abort Controller可以终止什么

在前端开发中,AbortController 是一个非常强大的 API,它提供了一种取消(中止)一个或多个 Web 请求或异步操作的机制。它主要用于取消那些可以通过 AbortSignal 监听取消事件的 API。

AbortController 的核心思想:

  1. 创建一个 AbortController 实例。
  2. 从该实例获取一个 AbortSignal 对象。
  3. 将这个 AbortSignal 传递给一个支持取消的异步操作。
  4. 当你想取消这个操作时,调用 AbortController 实例的 abort() 方法。这将触发 AbortSignal 上的 abort 事件,所有监听该事件的操作都会被取消。

AbortController 能中止哪些操作?

AbortController 主要用于中止那些支持 AbortSignal 作为选项的 Web API。最常见且最有用的场景是:

  1. fetch() 请求
  2. XMLHttpRequest (XHR) 请求
  3. EventSource 连接
  4. WebSocket 连接 (部分实现)
  5. FileReader 操作
  6. Image 加载
  7. Promise (通过手动检查信号状态)
  8. 自定义的异步操作

下面通过详细的代码示例来讲解如何使用 AbortController 中止这些操作。


详细代码讲解

1. 中止 fetch() 请求 (最常见用法)

fetch() API 是现代前端进行网络请求的首选。它原生支持 AbortSignal

js 复制代码
// 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;

// 监听 signal 的 abort 事件,可以在这里做一些清理工作
signal.addEventListener('abort', () => {
  console.log('Fetch request aborted!');
});

async function fetchData() {
  try {
    console.log('Starting fetch request...');
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', {
      method: 'GET',
      // 将 signal 传递给 fetch 的 options
      signal: signal
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log('Fetch data received:', data);
  } catch (error) {
    // Abort 导致的错误通常是 DOMException,name 为 'AbortError'
    if (error.name === 'AbortError') {
      console.log('Fetch was aborted by AbortController.');
    } else {
      console.error('Fetch error:', error);
    }
  }
}

// 启动 fetch 请求
fetchData();

// 模拟在一段时间后取消请求
setTimeout(() => {
  console.log('Attempting to abort fetch request...');
  controller.abort(); // 调用 abort() 方法取消请求
}, 100); // 100ms 后取消,通常请求还没完成

解释:

  • 我们创建了一个 controller 和它的 signal
  • fetch 请求的 signal 选项被设置为 controller.signal
  • controller.abort() 被调用时,signal 会触发 abort 事件,fetch 请求会立即终止,并抛出一个 AbortError
  • catch 块中,我们可以通过 error.name === 'AbortError' 来区分是中止错误还是其他网络错误。

2. 中止 XMLHttpRequest (XHR) 请求

虽然 fetch 更现代,但有时你可能仍然会遇到或需要使用 XHR。XHR 也支持 AbortSignal

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('XHR request aborted!');
});

function loadXHRData() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
  
  // 将 signal 绑定到 XHR
  xhr.signal = signal; // 注意:这是较新的 XHR 规范,旧浏览器可能不支持

  xhr.onload = function() {
    if (xhr.status >= 200 && xhr.status < 300) {
      console.log('XHR data received:', JSON.parse(xhr.responseText));
    } else {
      console.error('XHR error:', xhr.status, xhr.statusText);
    }
  };

  xhr.onerror = function() {
    console.error('XHR network error.');
  };

  xhr.onabort = function() {
    console.log('XHR was aborted by its signal.'); // XHR 自己的 abort 事件
  };

  xhr.send();

  // 也可以通过 XHR 实例的 abort 方法来取消,但 AbortController 更通用
  // signal.addEventListener('abort', () => xhr.abort()); // 兼容旧XHR
}

loadXHRData();

setTimeout(() => {
  console.log('Attempting to abort XHR request...');
  controller.abort();
}, 100);

解释:

  • 新版的 XHR 规范允许直接将 signal 赋值给 xhr.signal
  • 对于旧浏览器,你需要在 signal.addEventListener('abort', () => xhr.abort()) 中手动调用 xhr.abort()AbortController 只是提供了一个统一的取消机制,具体如何取消取决于被取消的 API。

3. 中止 EventSource 连接

EventSource 用于服务器发送事件 (SSE)。

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('EventSource connection aborted!');
});

function connectEventSource() {
  // EventSource 构造函数也支持 signal 选项
  const es = new EventSource('https://example.com/sse-stream', {
    signal: signal // 假设你的 SSE 服务支持通过 signal 中止
  });

  es.onopen = function() {
    console.log('EventSource connection opened.');
  };

  es.onmessage = function(event) {
    console.log('EventSource message:', event.data);
  };

  es.onerror = function(error) {
    console.error('EventSource error:', error);
    // 如果是中止导致的错误,通常会触发 onerror
    if (signal.aborted) {
      console.log('EventSource error was due to abort.');
    }
    es.close(); // 错误发生时关闭连接
  };

  // 也可以在 signal abort 时手动关闭 EventSource
  // signal.addEventListener('abort', () => es.close());
}

// 启动 EventSource 连接
// connectEventSource(); // 示例中不实际运行,因为需要一个真实的 SSE 服务

// 模拟在一段时间后取消连接
// setTimeout(() => {
//   console.log('Attempting to abort EventSource connection...');
//   controller.abort();
// }, 5000);

解释:

  • EventSource 构造函数同样接受 signal 选项。
  • signal 被中止时,EventSource 会关闭连接。

4. 中止 WebSocket 连接 (部分实现)

WebSocket 本身没有直接支持 AbortSignal 的选项。但是,你可以通过监听 signalabort 事件,然后在事件触发时手动调用 WebSocket.close() 来实现中止。

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('WebSocket connection aborted!');
});

function connectWebSocket() {
  const ws = new WebSocket('wss://echo.websocket.org'); // 示例 WebSocket 服务

  ws.onopen = function() {
    console.log('WebSocket connection opened.');
    ws.send('Hello WebSocket!');
  };

  ws.onmessage = function(event) {
    console.log('WebSocket message received:', event.data);
  };

  ws.onclose = function(event) {
    if (event.wasClean) {
      console.log(`WebSocket connection closed cleanly, code=${event.code}, reason=${event.reason}`);
    } else {
      console.error('WebSocket connection died unexpectedly.');
    }
    // 检查是否是由于 AbortController 导致的关闭
    if (signal.aborted) {
      console.log('WebSocket closed due to AbortController.');
    }
  };

  ws.onerror = function(error) {
    console.error('WebSocket error:', error);
  };

  // 当 signal 被中止时,手动关闭 WebSocket 连接
  signal.addEventListener('abort', () => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.close(1000, 'Aborted by AbortController'); // 1000 是正常关闭代码
    }
  });
}

connectWebSocket();

setTimeout(() => {
  console.log('Attempting to abort WebSocket connection...');
  controller.abort();
}, 3000);

解释:

  • 由于 WebSocket 不直接接受 signal,我们通过在 signalabort 事件监听器中手动调用 ws.close() 来实现取消。

5. 中止 FileReader 操作

FileReader 用于异步读取文件内容。

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('FileReader operation aborted!');
});

function readFile(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = (event) => {
      console.log('File read successfully.');
      resolve(event.target.result);
    };

    reader.onerror = (error) => {
      console.error('FileReader error:', error);
      reject(error);
    };

    reader.onabort = () => {
      console.log('FileReader operation was aborted.');
      reject(new DOMException('FileReader operation aborted', 'AbortError'));
    };

    // 监听 signal 的 abort 事件,并在触发时调用 reader.abort()
    signal.addEventListener('abort', () => {
      console.log('Aborting FileReader...');
      reader.abort();
    });

    reader.readAsText(file); // 或者 readAsDataURL, readAsArrayBuffer 等
  });
}

// 模拟一个文件对象 (在浏览器环境中,通常通过 <input type="file"> 获取)
const dummyFile = new Blob(['Hello, this is some dummy content for file reading.'], { type: 'text/plain' });
dummyFile.name = 'dummy.txt'; // 添加 name 属性,方便识别

async function runFileReaderExample() {
  try {
    console.log('Starting FileReader operation...');
    const resultPromise = readFile(dummyFile);

    // 模拟在读取过程中取消
    setTimeout(() => {
      console.log('Attempting to abort FileReader...');
      controller.abort();
    }, 10); // 很快取消

    const content = await resultPromise;
    console.log('File content:', content);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('FileReader operation was successfully aborted.');
    } else {
      console.error('FileReader example error:', error);
    }
  }
}

runFileReaderExample();

解释:

  • FileReader 实例本身有一个 abort() 方法。
  • 我们通过 signal.addEventListener('abort', () => reader.abort())AbortController 的取消事件与 FileReader 的取消方法关联起来。

6. 中止 Image 加载

Image 对象的加载也可以被中止。

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('Image loading aborted!');
});

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = url;

    img.onload = () => {
      console.log('Image loaded successfully.');
      resolve(img);
    };

    img.onerror = (error) => {
      console.error('Image loading error:', error);
      reject(error);
    };

    // 监听 signal 的 abort 事件,并在触发时取消图片加载
    signal.addEventListener('abort', () => {
      console.log('Aborting image loading...');
      img.src = ''; // 设置空 src 可以取消当前加载
      reject(new DOMException('Image loading aborted', 'AbortError'));
    });
  });
}

async function runImageLoadingExample() {
  try {
    console.log('Starting image loading...');
    // 使用一个较大的图片 URL,以便有时间取消
    const imageUrl = 'https://picsum.photos/seed/picsum/1000/1000'; // 随机大图

    const imagePromise = loadImage(imageUrl);

    // 模拟在加载过程中取消
    setTimeout(() => {
      console.log('Attempting to abort image loading...');
      controller.abort();
    }, 50); // 50ms 后取消

    const loadedImage = await imagePromise;
    document.body.appendChild(loadedImage);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Image loading was successfully aborted.');
    } else {
      console.error('Image loading example error:', error);
    }
  }
}

runImageLoadingExample();

解释:

  • Image 对象没有内置的 abort() 方法,但通过将 src 设置为空字符串可以中断当前加载。
  • 我们同样通过 signal.addEventListener 来关联取消逻辑。

7. 中止自定义的异步操作 (Promise)

对于你自己的 Promise 或其他异步操作,你可以通过检查 signal.aborted 状态或监听 signalabort 事件来手动实现取消逻辑。

js 复制代码
const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener('abort', () => {
  console.log('Custom async operation aborted!');
});

function longRunningOperation(signal) {
  return new Promise((resolve, reject) => {
    let count = 0;
    const intervalId = setInterval(() => {
      // 每次迭代都检查 signal 是否被中止
      if (signal.aborted) {
        clearInterval(intervalId);
        console.log('Custom operation detected abort signal.');
        reject(new DOMException('Operation aborted', 'AbortError')); // 抛出 AbortError
        return;
      }

      count++;
      console.log(`Custom operation step ${count}...`);
      if (count === 5) {
        clearInterval(intervalId);
        console.log('Custom operation completed.');
        resolve('Operation finished successfully!');
      }
    }, 500);
  });
}

async function runCustomOperationExample() {
  try {
    console.log('Starting custom operation...');
    const result = await longRunningOperation(signal);
    console.log('Custom operation result:', result);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Custom operation was successfully aborted.');
    } else {
      console.error('Custom operation error:', error);
    }
  }
}

runCustomOperationExample();

setTimeout(() => {
  console.log('Attempting to abort custom operation...');
  controller.abort();
}, 2200); // 2.2 秒后取消,在第 4 或 5 步时

解释:

  • 在自定义的异步操作中,我们定期检查 signal.aborted 属性。
  • 如果 signal.abortedtrue,则停止操作并 reject 一个 AbortError
  • 这种模式可以应用于任何需要中断的长时间运行的 JavaScript 任务,例如复杂的计算、动画循环等。

总结

AbortController 提供了一个标准的、统一的机制来取消异步操作。它的核心是 AbortSignal,任何支持 signal 选项的 Web API 都可以直接被 AbortController 中止。对于那些不直接支持 signal 的 API,你也可以通过监听 signalabort 事件,在事件触发时手动调用该 API 的取消方法(如 WebSocket.close()FileReader.abort() 等)来实现取消。

这对于管理用户交互(例如,用户在请求完成前切换页面)、优化资源使用和提高应用程序响应性都非常有用。

相关推荐
半点寒12W24 分钟前
微信小程序实现路由拦截的方法
前端
某公司摸鱼前端1 小时前
uniapp socket 封装 (可拿去直接用)
前端·javascript·websocket·uni-app
要加油哦~1 小时前
vue | 插件 | 移动文件的插件 —— move-file-cli 插件 的安装与使用
前端·javascript·vue.js
小林学习编程1 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot
柳鲲鹏1 小时前
WINDOWS最快布署WEB服务器:apache2
服务器·前端·windows
weixin-a153003083162 小时前
【playwright篇】教程(十七)[html元素知识]
java·前端·html
ai小鬼头3 小时前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
一只叫煤球的猫4 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
vvilkim4 小时前
Electron 自动更新机制详解:实现无缝应用升级
前端·javascript·electron
vvilkim4 小时前
Electron 应用中的内容安全策略 (CSP) 全面指南
前端·javascript·electron