我有一个简单的 UI,其中有一个按钮可以触发对 API 的获取请求。
<button type="button"id="fetch">Fetch products</button>
我想实现一些逻辑(使用 AbortController),以便任何重新获取都会取消之前正在进行的请求。为了模拟网络延迟(并确保我的中止逻辑正常工作),我将获取逻辑包装到延迟获取操作的承诺中。在每次点击事件中,我都会检查控制器是否已存在以取消其相关请求。
注意我不想禁用按钮或消除用户的点击此处。
这是基本代码:
const btnEl = document.querySelector('#fetch');
const API_BASE_URL =
'https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store';
let controller;
const onClick = async () => {
try {
const products = await fetchProducts();
console.log('Products:', products);
} catch (error) {
console.error('Failed to fetch products.', error?.message ?? error);
}
};
btnEl.addEventListener('click', () => onClick());
❌ 这是我最初尝试的业务逻辑。我期望 Promise 的执行器函数能够关闭外部作用域的控制器值。显然情况并非如此......看起来新的获取请求在有机会触发之前改变了先前的中止控制器。我不知道为什么这不起作用🤔...
function fetchProducts() {
return fetchDataWithDelay(API_BASE_URL + '/products.json');
}
// Pretend data fetching is hitting the network.
function fetchDataWithDelay(url, delay = 3000) {
if (!url) throw new Error('URL is required.');
if (controller) {
controller.abort('Fetch aborted by the user.');
console.log('Fetch aborted.');
}
controller = new AbortController();
return new Promise((resolve, reject) => {
console.log(`Fetching data with ${delay}ms delay...`);
setTimeout(async () => {
try {
const response = await fetch(url, { signal: controller.signal });
response.ok
? resolve(await response.json())
: reject(new Error(`Request failed with status ${response.status}`));
} catch (error) {
reject(error);
}
}, delay);
});
}
指定延迟内的多次点击会导致尽可能多的获取请求。以下是我在 3000 毫秒内 2 次点击事件后得到的控制台日志:
Fetching data with 3000ms delay...
Fetch aborted.
Fetching data with 3000ms delay...
Products:
(12) [{...}, {...}, {...}, ..., {...}]
Products:
(12) [{...}, {...}, {...}, ..., {...}]
✅ 我设法通过将中止控制器相关逻辑提取到调用者函数来使其工作:
function fetchProducts() {
// Moved the controller logic here...
if (controller) {
controller.abort('Fetch aborted by the user.');
console.log('Fetch aborted.');
}
controller = new AbortController();
// ... and passed the controller as a function parameter to the delayed fetch function
return fetchDataWithDelay(API_BASE_URL + '/products.json', controller);
}
function fetchDataWithDelay(url, abortController, delay = 3000) {
if (!url) throw new Error('URL is required.');
return new Promise((resolve, reject) => {
console.log(`Fetching data with ${delay}ms delay...`);
setTimeout(async () => {
try {
const response = await fetch(url, { signal: abortController.signal });
response.ok
? resolve(await response.json())
: reject(new Error(`Request failed with status ${response.status}`));
} catch (error) {
reject(error);
}
}, delay);
});
}
这里我得到了 3000 毫秒内 2 次点击事件后的预期输出:
Fetching data with 3000ms delay...
Fetch aborted.
Fetching data with 3000ms delay...
Failed to fetch products. Fetch aborted by the user.
Products:
(12) [{...}, {...}, {...}, ..., {...}]
但是这次不完全确定为什么会起作用(闻起来像关闭......)😅
任何见解将不胜感激。
感谢您的宝贵时间🙏
问题出在您的
setTimeout()
上,在工作示例中,您使用参数 abortController
创建一个副本,因此在 setTimeout
的回调中,它是正确的预期有效控制器。
当您不使用该功能时,当您单击时,您会在将控制器传递给
fetch()
🤷u200d♂️之前覆盖控制器,因为超时尚未发生。
因此,如果删除
setTimeout()
,第一个代码就可以正常工作。