在前端开发中,AbortController
是一个非常强大的 API,它提供了一种取消(中止)一个或多个 Web 请求或异步操作的机制。它主要用于取消那些可以通过 AbortSignal
监听取消事件的 API。
AbortController
的核心思想:
- 创建一个
AbortController
实例。 - 从该实例获取一个
AbortSignal
对象。 - 将这个
AbortSignal
传递给一个支持取消的异步操作。 - 当你想取消这个操作时,调用
AbortController
实例的abort()
方法。这将触发AbortSignal
上的abort
事件,所有监听该事件的操作都会被取消。
AbortController
能中止哪些操作?
AbortController
主要用于中止那些支持 AbortSignal
作为选项的 Web API。最常见且最有用的场景是:
fetch()
请求XMLHttpRequest
(XHR) 请求EventSource
连接WebSocket
连接 (部分实现)FileReader
操作Image
加载Promise
(通过手动检查信号状态)- 自定义的异步操作
下面通过详细的代码示例来讲解如何使用 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
的选项。但是,你可以通过监听 signal
的 abort
事件,然后在事件触发时手动调用 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
,我们通过在signal
的abort
事件监听器中手动调用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
状态或监听 signal
的 abort
事件来手动实现取消逻辑。
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.aborted
为true
,则停止操作并reject
一个AbortError
。 - 这种模式可以应用于任何需要中断的长时间运行的 JavaScript 任务,例如复杂的计算、动画循环等。
总结
AbortController
提供了一个标准的、统一的机制来取消异步操作。它的核心是 AbortSignal
,任何支持 signal
选项的 Web API 都可以直接被 AbortController
中止。对于那些不直接支持 signal
的 API,你也可以通过监听 signal
的 abort
事件,在事件触发时手动调用该 API 的取消方法(如 WebSocket.close()
、FileReader.abort()
等)来实现取消。
这对于管理用户交互(例如,用户在请求完成前切换页面)、优化资源使用和提高应用程序响应性都非常有用。