取消/中止延迟的获取请求

问题描述 投票:0回答:1

我有一个简单的 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) [{...}, {...}, {...}, ..., {...}]

但是这次不完全确定为什么会起作用(闻起来像关闭......)😅

任何见解将不胜感激。

感谢您的宝贵时间🙏

javascript fetch closures delayed-execution abortcontroller
1个回答
0
投票

问题出在您的

setTimeout()
上,在工作示例中,您使用参数
abortController
创建一个副本,因此在
setTimeout
的回调中,它是正确的预期有效控制器。

当您不使用该功能时,当您单击时,您会在将控制器传递给

fetch()
🤷u200d♂️之前覆盖控制器,因为超时尚未发生。

因此,如果删除

setTimeout()
,第一个代码就可以正常工作。

© www.soinside.com 2019 - 2024. All rights reserved.