如何取消Promise?

一个常见的误解是Promise本身可以被“取消”。实际上,标准的Promise一旦创建,就会开始执行,并且没有内置的中止或取消方法。所谓的“取消Promise”,通常指的是我们不再关心其结果,并希望避免其后续的回调执行,同时可能中止其背后的异步操作(如网络请求)。这需要结合一些模式来实现。

利用可取消的包装函数

一种常见模式是创建一个可取消的Promise包装器。其核心思想是,包装函数返回一个Promise和一个取消函数。当取消函数被调用时,它会使返回的Promise提前拒绝。

function cancellablePromise(taskPromise) {
  let isCancelled = false;
  const promise = new Promise(async (resolve, reject) => {
    try {
      const result = await taskPromise;
      if (isCancelled) {
        reject(new Error('Promise was cancelled'));
      } else {
        resolve(result);
      }
    } catch (error) {
      if (!isCancelled) {
        reject(error);
      }
    }
  });
  return {
    promise,
    cancel: () => {
      isCancelled = true;
    }
  };
}

// 使用示例
const { promise, cancel } = cancellablePromise(fetch('/api/data'));
promise.then(data => console.log(data)).catch(e => console.log(e.message));
// 在某个时刻调用 cancel()
cancel(); // 这将导致 promise 被拒绝,并打印 'Promise was cancelled'

这种方法“取消”的是对外部结果的处理,而非真正停止内部的异步操作(例如,fetch请求可能仍在继续)。它只是阻止了成功或失败的回调执行。

结合AbortController取消fetch请求

对于特定的异步操作,如fetch,我们可以利用Web API AbortController来实现真正的操作取消。这是目前取消网络请求的标准方式。

const controller = new AbortController();
const signal = controller.signal;

fetch('/api/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求被取消');
    } else {
      console.error('其他错误', err);
    }
  });

// 在需要取消时调用 abort
controller.abort();

当调用controller.abort()时,与signal关联的所有fetch请求都会被中止,Promise会以一个AbortError拒绝。这真正停止了底层的网络活动。

使用race实现超时取消

通过结合Promise.race(),我们可以轻松实现一个超时取消的机制。

function withTimeout(promise, timeout) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('操作超时')), timeout);
  });
  return Promise.race([promise, timeoutPromise]);
}

// 使用
withTimeout(fetch('/api/slow'), 5000)
  .then(data => console.log('成功'))
  .catch(e => console.log(e.message)); // 如果5秒内未完成,输出‘操作超时’

这里的“取消”是由超时触发的,超时的Promise在竞速中获胜,导致整个race提前拒绝,但原始的异步操作可能仍在后台运行。

在项目实践中,你需要根据场景选择策略。对于需要真正中止网络请求的,使用AbortController。对于通用的异步任务,若只需忽略结果,可使用标志位包装器。注意,取消一个Promise更多的是关于“忽略”或“超时”,而非字面意义上的停止执行。清晰的取消逻辑对于提升应用性能和用户体验至关重要。

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

    暂无评论内容