Skip to content
Advertisement

How can I do I cancel javascript await sleep?

The most common implementation of a sleep function in javascript is returning a Promise after setTimeout resolves:

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

I have for loop with await sleep to keep it from executing too fast, such as not requesting xhr too fast. I also have a isBreak flag elsewhere to tell me when to stop the for loop. However, the issue I have is that when I break the for loop, the previous await sleep has already executed and is holding up the for loop. Is there a better way of breaking the for loop and also terminating the await sleep instantaneously?

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;
}

Is there a way for me to signal for early

Advertisement

Answer

In JS, when an await operation starts, it can no longer be interrupted; it will wait until its operand promise is settled.

So, you have to make the promise you’re awaiting cancelable in some way.

Unfortunately, your code can’t get notified about a variable reassignment (when you set isBreak to true), and polling it would be inefficient.

Instead of a flag, you could use an AbortSignal (which was invented for this purpose), and make your sleep accept one:

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);
  });
}

Then, you use it like this:

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;
  }
}

An AbortSignal works well with fetch as well, in case you have to cancel that too.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement