问题
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 normally extractDate
function should take longer than 2 seconds, so I actually want to await for everything inside setInterval
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());
}
});
});
}
回答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);
回答2:
Use the following code:
setInterval(async () => {
await fetch("https://www.google.com/")
}, 100);
回答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.
回答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/
回答5:
If someone wants an updated solution there is react-timeout which cancels any lingering timers automatically when wrapped component is unmounted.
More info
https://www.npmjs.com/package/react-timeout
npm i react-timeout
While using await you can do something like this
handleClick = async() => {
try {
await this.props.getListAction().then(()=>{
this.timeOut = this.props.setTimeOut(this.handleClick, 10000);
});
} catch(err) {
clearTimeout(this.timeOut);
}
}
回答6:
When using an async function as an argument to setInterval
or setTimeout
, make sure you wrap any operations that can throw errors in a try/catch to avoid un-handled errors and, on Node.js, do not call process.exit
as it will pre-empt the event loop without any respect for timers. More detail in Async, Promises, and Timers in Node.js.
来源:https://stackoverflow.com/questions/51830200/how-to-await-inside-setinterval-in-js