How to await inside setInterval in JS?
Solution 1
Turn the interval function into a recursive setTimeout
function instead, that way you can initialize a timeout for the next iteration once the function has finished.
async function doScroll() {
window.scrollBy(0, scrollHeight);
const scrollTop = document.documentElement.scrollTop;
let lastDate = null;
if (maxDate) {
const html = new XMLSerializer().serializeToString(document.doctype) + document.documentElement.outerHTML;
await extractDate(html).then((date) => {
lastDate = date;
});
}
if (scrollTop === lastScrollTop ||
(maxDate && lastDate && maxDate.getTime() >= lastDate.getTime())) {
// No need to `clearInterval`:
resolve();
} else {
lastScrollTop = scrollTop;
// Recursive setTimeout:
setTimeout(doScroll, 2000); // <------------------
}
}
setTimeout(doScroll, 2000);
Solution 2
Use the following code:
setInterval(async () => {
await fetch("https://www.google.com/")
}, 100);
Solution 3
I generally opt for this solution. I think it's cleaner:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function loop() {
while (/* condition */) {
/* code to wait on goes here (sync or async) */
await delay(100)
}
}
Your loop
function will return a promise. You can wait for it to stop looping, or you can discard it.
Solution 4
Make the interval a function instead and use setTimeout to queue the future function call.
const interval = async function () { // instead of setInterval
Then use setTimeout function where you want to to queue the future call:
setTimeout(interval, 2000);
Fiddle example: http://jsfiddle.net/t9apy3ec/5/
tinker
Updated on July 04, 2020Comments
-
tinker almost 4 years
I have a code segment that looks like this:
async function autoScroll(page, maxDate = null) { await page.evaluate(async () => { await new Promise(async (resolve, reject) => { try { const scrollHeight = document.body.scrollHeight; let lastScrollTop = 0; const interval = setInterval(async () => { window.scrollBy(0, scrollHeight); const scrollTop = document.documentElement.scrollTop; let lastDate = null; if (maxDate) { const html = new XMLSerializer().serializeToString(document.doctype) + document.documentElement.outerHTML; await extractDate(html).then((date) => { lastDate = date; }); } if (scrollTop === lastScrollTop || (maxDate && lastDate && maxDate.getTime() >= lastDate.getTime())) { clearInterval(interval); resolve(); } else { lastScrollTop = scrollTop; } }, 2000); } catch (err) { console.error(err); reject(err.toString()); } }); }); }
Where
extractDate
method has the following form:function extractDate(html) { return new Promise((resolve, reject) => { // Rest removed for brevity. resolve(result); }); }
Now the problem is that, my code keeps scrolling, but it doesn't wait for the other stuff inside
setInterval
to finish, as it keeps scrolling every 2 seconds, but normallyextractDate
function should take longer than 2 seconds, so I actually want to await for everything insidesetInterval
to finish before making the call to the new interval.Because of the async nature of stuff, I didn't manage to
console.log
stuff so see the behavior of the code.So, how can I make sure that everything inside
setInterval
finishes before making the next interval call?EDIT:
This solution using
setTimeout
scrolls just once and throws unhandled promise rejection error with puppeteer.async function autoScroll(page, maxDate = null) { await page.evaluate(async () => { await new Promise(async (resolve, reject) => { try { const scrollHeight = document.body.scrollHeight; let lastScrollTop = 0; const interval = async function() { window.scrollBy(0, scrollHeight); const scrollTop = document.documentElement.scrollTop; let lastDate = null; if (maxDate) { const html = new XMLSerializer().serializeToString(document.doctype) + document.documentElement.outerHTML; await extractDate(html).then((date) => { lastDate = date; }); } if (scrollTop === lastScrollTop || (maxDate && lastDate && maxDate.getTime() >= lastDate.getTime())) { resolve(); } else { lastScrollTop = scrollTop; setTimeout(interval, 2000); } } setTimeout(interval, 2000); } catch (err) { console.error(err); reject(err.toString()); } }); }); }
-
CertainPerformance over 5 yearsYou would put all that inside the
try
block - everything else would be the same -
tinker over 5 yearsPlease check my edited code with your solution. I tried that but using that approach it just scrolls once. No matter whether I comment out the if statement that includes the await call. Where previously it was scrolling all the time until if condition was satisfied.
-
Gustav G over 5 yearsTry moving the interval function to the top of the parent promise function, and move the try catch block inside the interval. And at the promise line, try returning the promise and remove the async keyword from the promise function.
-
tinker over 5 yearsCan you write it here as an answer.
-
president over 4 yearsThank you! I was searching for this solution very long time)
-
Admin about 4 yearsNo one asked about react
-
genuinefafa about 4 yearsbeware of github.com/typescript-eslint/typescript-eslint/blob/v2.28.0/… setInterval will receive a promise which it doesn't understand
-
Nir O. almost 4 yearswhat is this resolve() call ? it is not defined in the block. also, shouldn't it be scroll() instead of scroll ?
-
CertainPerformance almost 4 years@NirO. This is what the code inside the
try
block should be.resolve
is defined outside, inawait new Promise(async (resolve, reject) => {
-
Robert Hollon over 3 yearsThis is absolutely smooth. I'm transitions my calls out of mssqlserver into node, and this resolves the logic steps that functions posted by others fail to account for: fully complete the statement before initiating any delay.
-
Yuriy Galanter about 3 yearsThis is beautiful
-
Rip3rs over 2 yearsSlick! thanks for this!
-
Rip3rs over 2 yearsUsually is not a good thing to mix
await
andthen
syntaxes as it will create more confusion. -
Murrah over 2 yearsBrilliant solution. Thanks!