我该如何取消 JavaScript 的 await sleep?

5
在JavaScript中,最常见的sleep函数实现方式是在setTimeout解析后返回一个Promise:
function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

我有一个for循环,其中使用await sleep来使其执行速度不快,例如不会太快地请求xhr。我还有一个isBreak标志在其他地方告诉我何时停止for循环。但是,我的问题是,当我打破for循环时,先前的await sleep已经执行,并且正在阻止for循环的执行。有没有更好的方法来打破for循环并立即终止await sleep?

const items = [];

let isBreak = false; // Somewhere else in the application

for (const item of items) {
  if (isBreak) break;

  // Do something, like xhr request
  await sleep(15000); // 15 seconds sleep

  if (isBreak) break;
}

有没有一种方法可以提前发出信号?


如果你真的需要Promise实现,FZs的回答非常出色。 - Touffy
1
然而,你有没有考虑过只是使用 setInterval 并取消该间隔? - Touffy
2个回答

7
在JS中,当一个await操作开始后,它就不能被打断了;只能等待其操作数 (operand) promise 状态已被解决。
因此,您必须以某种方式使您正在等待的promise可取消。
不幸的是,您的代码无法得到有关变量重新分配(当您将isBreak设置为true时)的通知,轮询会很低效。
可以使用一个专门为此目的而发明的AbortSignal,并让您的sleep接受一个AbortSignal,而不是一个标志(flag)。
function sleep(ms, signal) {
  return new Promise((resolve, reject) => {
    signal.throwIfAborted();

    const timeout = setTimeout(() => {
      resolve();
      signal.removeEventListener('abort', abort);
    }, ms);

    const abort = () => {
      clearTimeout(timeout);
      reject(signal.reason);
    }

    signal.addEventListener('abort', abort);
  });
}

接下来,你可以按照以下方式使用它:

const items = [];

const isBreak = new AbortController(); // Somewhere else in the application, call `isBreak.abort()`

try {
  for (const item of items) {
    // Do something, like xhr request
    await sleep(15000, isBreak.signal); // 15 seconds sleep
  }
} catch (e) {
  if (e.name === 'TimeoutError') {
    // Handle a cancellation
    console.log('Cancelled');
  } else {
    // Not a cancellation, rethrow it
    throw e;
  }
}

一个AbortSignal在使用 fetch 时也能很好地工作,以防你也需要取消它。

我完全忘记了添加事件信号。这应该可以正常工作。谢谢。 - moredrowsy
TypeScript版本与单元测试:https://gist.github.com/tkrotoff/c6dd1cabf5570906724097c6e3f65a12 - tanguy_k

-1

这是我在过去的博客中找到并进行了调整的答案。它与FZ的答案相似,用法也相同,只是提供了另一种选择。

相关链接:如何在Javascript Promise内取消超时?

function sleep(ms, abortSignal) {
  return new Promise((resolve, reject) => {
    signal.addEventListener("abort", abort);
    if(abortSignal.aborted){
      abort();
    }
    const timeout = setTimeout(end, ms);

    function abort() {
      clearTimeout(timeout);
      abortSignal.removeEventListener("abort", abort);
      reject(new Error("sleep aborted"));
    }

    function end() {
      abortSignal.removeEventListener("abort", abort);
      resolve();
    }
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接