I am running a sequence of functions on a page in Puppeteer. If one of those functions throws I retry the whole sequence a couple of times because the systems I'm using are not reliable. It's easier than handling all possible exceptions.
Sometimes instead of throwing, one of those steps will just hang so I want to have a timeout for safety, and retry as if an exception happened.
If I don't wrap my sequence in the below asyncCallWithTimeout()
everything works correctly and resolves to what I want (an array). However, when I do use that function I'm left with an unresolved function that I passed as asyncPromise
.
For the life of me, I can't figure out why Promise.race()
would resolve with an unresolved asyncPromise
that I passed. What am I missing?
Here's the function to time out other async functions when they run too long:
async function asyncCallWithTimeout(asyncPromise, timeLimit) {
let timeoutHandle;
const timeoutPromise = new Promise((_resolve, reject) => {
timeoutHandle = setTimeout(
() => reject(new Error('Async call timeout limit reached')),
timeLimit
);
});
return Promise.race([asyncPromise, timeoutPromise]).then((result) => {
clearTimeout(timeoutHandle); // .then() looks to be executed every time even if timeout is set to 1 ms
return result;
});
};
const timer = ms => new Promise(r => setTimeout(r, ms));
const shouldResolve = asyncCallWithTimeout(timer(1000), 2000).then(console.log).catch(console.error);
const shouldReject = asyncCallWithTimeout(timer(2000), 1000).then(console.log).catch(console.error);
Here's the retry function in case it's relevant:
async function retry(promiseFactory, retryCount) {
try {
return await promiseFactory();
} catch (error) {
if (retryCount <= 0) {
throw error;
}
return await retry(promiseFactory, retryCount - 1);
}
};
I'm using it this way:
async function checkT(browser, url) {
const tUrl = `https://example.com/?url=${url}`;
return retry(async () => {
const browserPage = await browser.newPage();
try {
return asyncCallWithTimeout(async () => { // works if this is commented
await browserPage.goto(tUrl);
await doSomething(browserPage);
await waitForSomething(browserPage);
const results = await getResults(browserPage);
await browserPage.close();
return results;
}, 10000); // works if this is commented
} catch (error) {
await browserPage.close();
throw error;
}
}, 3);
};
And then I call it elsewhere like so (using p-queue which is irrelevant):
for (const page of pages) {
queue.add(() => checkT(browser, page.url)
.then(async (results) => {
// results should be an array but is a function and not iterable hence fails below
for (const result of results.data) {
// do something with the result
}
})
);
};