Reputation: 418
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
Upvotes: 5
Views: 970
Reputation: 18619
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 await
ing 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.
Upvotes: 8
Reputation: 3073
An answer i found in a blog in the past that i adjusted. It is similiar to FZs answer. Same usage, too. Just to give an alternative.
relevant too: How to cancel timeout inside of 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();
}
});
}
Upvotes: -1