在JavaScript的异步编程中,Promise对象扮演着核心角色,它允许我们以更优雅的方式处理延迟和异步操作。但是,一个常见的问题是,一旦创建了Promise,就没有一个直接的方式来取消它。这不仅限制了我们的控制能力,也可能导致资源浪费。本文将探讨如何在JavaScript中优雅地“取消”Promise,以及如何有效地管理Fetch和XMLHttpRequest请求的取消。我们将介绍一些实用的技巧和最新的API,帮助你更好地控制异步流程。跟随本文,你将学会如何通过Promise.withResolvers()和AbortController来实现取消操作,以及如何处理取消后的清理工作。让我们一起揭开取消Promise的神秘面纱,掌握这些实用的编程技巧。
如何取消 Promise
目前,JavaScript 的 Promise 并没有提供一个 API 来取消一个普通的 Promise。所以,我们接下来要讨论的是如何丢弃/忽略 Promise 的结果。
现在可以使用的一个新 API 是 Promise.withResolvers()。它返回一个包含一个新的 Promise 对象和两个用于解决或拒绝它的函数的对象。
代码看起来是这样的:
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
现在我们可以这样做:
const { promise, resolve, reject } = Promise.withResolvers();
所以我们可以利用这个来暴露一个 cancel 方法:
const buildCancelableTask = <T>(asyncFn: () => Promise<T>) => {
let rejected = false;
const { promise, resolve, reject } = Promise.withResolvers<T>();
return {
run: () => {
if (!rejected) {
asyncFn().then(resolve, reject);
}
return promise;
},
cancel: () => {
rejected = true;
reject(new Error('CanceledError'));
},
};
};
然后我们可以使用以下测试代码:
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
const ret = buildCancelableTask(async () => {
await sleep(1000);
return 'Hello';
});
(async () => {
try {
const val = await ret.run();
console.log('val: ', val);
} catch (err) {
console.log('err: ', err);
}
})();
setTimeout(() => {
ret.cancel();
}, 500);
在这里,我们预设任务至少需要 1000ms,但我们在接下来的 500ms 内取消它,所以你将看到:
media.beehiiv.com/cdn-cgi/ima…
请注意,这不是真正的取消,而是一个早期拒绝。原始的 asyncFn() 将继续执行直到它解决或拒绝,但这并不重要,因为我们用 Promise.withResolvers<T>() 创建的 Promise 已经被拒绝了。
如何取消 Fetch
AbortController 是 Fetch API 的一部分,它提供了一种机制来取消一个或多个 Web 请求。AbortController 允许你通过一个信号(AbortSignal)来控制一个或多个请求的取消行为。当 AbortSignal 的 abort 方法被调用时,所有关联到这个信号的请求都会收到取消的指令。
以下是如何使用 AbortController 来取消一个 Fetch 请求的步骤:
1. 创建一个 AbortController 实例
const controller = new AbortController();
2. 获取 AbortSignal 对象
AbortController 实例有一个 signal 属性,它是一个 AbortSignal 对象,你可以将它传递给 Fetch 请求。
const { signal } = controller;
3. 将 AbortSignal 传递给 Fetch 请求
当你发起一个 Fetch 请求时,将 signal 对象作为选项传递给请求。
fetch(url, { signal })
.then(response => {
// 处理响应
})
.catch(error => {
// 处理错误,包括取消错误
if (error.name === 'AbortError') {
console.log('Fetch aborted');
}
});
4. 取消请求
当你想要取消请求时,调用 AbortController 的 abort 方法。这会触发所有关联到这个 AbortController 的 AbortSignal 的请求取消。
// 稍后取消请求
setTimeout(() => {
controller.abort();
}, 3000); // 3秒后取消请求
完整示例
const controller = new AbortController();
const { signal } = controller;
fetch('https://example.com/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
// 处理取消逻辑
console.log('Fetch aborted');
} else {
// 处理其他错误
console.error('Fetch error:', error);
}
});
// 3秒后取消请求
setTimeout(() => {
controller.abort();
}, 3000);
使用 AbortController 的好处是,你可以对多个请求使用同一个 AbortController 来控制它们的取消行为。这对于同时取消多个相关请求非常有用,比如在用户离开页面或取消某个操作时。
请注意,AbortController 只能用于支持 AbortSignal 的 API,比如 Fetch API。对于 XMLHttpRequest 或其他不支持 AbortSignal 的 API,你不能直接使用 AbortController 来取消请求。
如何取消 XHR 请求
对于取消 XMLHttpRequest (XHR) 请求,abort() 方法是最直接的方式,也是唯一的标准方法来取消正在进行的 HTTP 请求。没有其他内置的 JavaScript 方法可以取消 XHR 请求。然而,你可以采取一些策略来管理和控制请求,尽管这些不是直接取消请求的方法:
1. 控制请求触发
在发送请求之前,你可以设置一个标志来控制是否发送请求:
var xhr;
var isRequestActive = true;
function sendRequest() {
if (!isRequestActive) return;
xhr = new XMLHttpRequest();
xhr.open('GET', 'your-url-here', true);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Request successful:', xhr.responseText);
} else {
console.error('Request failed:', xhr.status);
}
};
xhr.onerror = function() {
console.error('Request failed');
};
xhr.send();
}
function cancelRequest() {
isRequestActive = false;
if (xhr) {
xhr.abort();
}
}
// 发送请求
sendRequest();
// 取消请求
cancelRequest();
2. 超时控制
你可以设置一个超时来自动取消请求,如果请求在一定时间内没有完成:
var xhr;
var timeoutId;
function sendRequest() {
xhr = new XMLHttpRequest();
xhr.open('GET', 'your-url-here', true);
xhr.onload = function() {
clearTimeout(timeoutId);
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Request successful:', xhr.responseText);
} else {
console.error('Request failed:', xhr.status);
}
};
xhr.onerror = function() {
clearTimeout(timeoutId);
console.error('Request failed');
};
xhr.send();
// 设置超时
timeoutId = setTimeout(function() {
xhr.abort();
console.log('Request timed out and was aborted');
}, 5000); // 5秒后超时
}
// 发送请求
sendRequest();
3. 资源清理
在某些情况下,你可能想要在请求被取消后执行一些清理工作,比如释放资源或更新 UI:
var xhr;
function sendRequest() {
xhr = new XMLHttpRequest();
xhr.open('GET', 'your-url-here', true);
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Request successful:', xhr.responseText);
} else {
console.error('Request failed:', xhr.status);
}
};
xhr.onerror = function() {
console.error('Request failed');
};
xhr.onabort = function() {
console.log('Request was aborted');
};
xhr.send();
}
function cancelRequest() {
if (xhr) {
xhr.abort();
// 执行清理工作
cleanupResources();
}
}
function cleanupResources() {
// 清理资源
console.log('Resources cleaned up');
}
// 发送请求
sendRequest();
// 取消请求
cancelRequest();
这些方法提供了额外的控制和灵活性,但它们并不是取消请求的替代方法。abort() 仍然是唯一直接取消 XHR 请求的标准方法。