如何实现请求超时控制?

在网络通信中,为异步操作设置超时是保障应用健壮性的基本要求。一个没有超时机制的请求可能会无限期挂起,消耗资源并阻塞用户界面。在JavaScript中,我们有几种有效的方法来为Promise包裹的异步操作添加超时控制。

利用Promise.race实现超时

最经典的超时模式是使用Promise.race()。它让原始请求的Promise和一个在指定时间后拒绝的“超时Promise”进行竞赛。谁先落定,就采用谁的结果。

function fetchWithTimeout(url, timeout = 5000) {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`请求超时 (${timeout}ms)`)), timeout);
  });
  return Promise.race([fetchPromise, timeoutPromise]);
}

// 使用
fetchWithTimeout('https://api.example.com/data', 3000)
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error.message)); // 可能是网络错误或“请求超时 (3000ms)”

这种方法简洁有效,被广泛应用于各种场景。但需要注意,Promise.race只会采用第一个落定的Promise,它并不会取消已经发出的fetch请求,该请求可能仍在后台进行。

结合AbortController实现真正取消

对于fetch请求,现代浏览器支持AbortController API。它不仅可以实现超时,还能真正中止底层的网络请求。

async function fetchWithAbortTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      throw new Error(`请求在 ${timeout}ms 后被取消`);
    }
    throw error;
  }
}

这里创建了一个AbortController,并将其signal传递给fetch。设置一个定时器,在超时后调用controller.abort()。这会中断fetch请求,并使其Promise以AbortError拒绝。最后,记得清理定时器。

封装通用的超时包装函数

我们可以创建一个通用的高阶函数,为任何返回Promise的操作添加超时能力。

function withTimeout(promise, timeout, errorMessage = '操作超时') {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => reject(new Error(errorMessage)), timeout);
    promise.then(resolve).catch(reject).finally(() => clearTimeout(timeoutId));
  });
}

// 用于任何Promise
const apiCall = fetch('/api/data').then(r => r.json());
withTimeout(apiCall, 3000).then(handleData).catch(handleError);

这个包装器不依赖于fetchAbortController,适用于任何Promise。它同样不取消原操作,只是提前拒绝了返回的Promise。

在实际项目中,如果目标是现代浏览器且需要真正节省网络资源,应优先使用AbortController方案。对于通用的Promise操作或Node.js环境(许多I/O操作不支持中止),使用Promise.race或通用包装器是合适的选择。超时时间的设置需要根据具体网络环境和业务容忍度仔细权衡。

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容