I’m struggling with a Puppeteer issue where my page evaluation returns undefined instead of waiting for async operations to complete.
I have some code running inside page.evaluate()
that needs to handle asynchronous operations. The problem is that instead of waiting for my async function to finish and return a value, Puppeteer just gives me undefined immediately.
Here’s what I’m trying to do:
const puppeteer = require('puppeteer');
async function runTest(value) {
const browser = await puppeteer.launch({
headless: false,
ignoreHTTPSErrors: true
});
const page = await browser.newPage();
const result = await page.evaluate((inputValue) => {
// synchronous setup code here...
function processValue(val) {
if (val > 1) {
var delayedProcess = setTimeout(function () {
if (val > 8) {
console.log('value exceeds threshold');
var resultData = 'high';
return Promise.resolve(resultData);
}
if (val > 3 && val < 8) {
console.log('value in middle range');
var resultData = 'medium';
return Promise.resolve(resultData);
}
else {
val++;
processValue(val);
}
}, 1500);
}
}
processValue(inputValue);
}, value);
console.log(result); // shows undefined instead of actual result
}
runTest(3);
I know Puppeteer can handle promises because this simple example works fine:
const output = await page.evaluate(() => {
return Promise.resolve(5 * 4);
});
console.log(output); // correctly shows "20"
From what I understand, Puppeteer should automatically wait for promises to resolve before returning the result. But my code isn’t working as expected. What am I doing wrong here?
Update: I managed to solve this problem!
Your main problem stems from the recursive structure and how you’re handling the promise chain. The processValue
function creates a setTimeout but never properly returns or awaits the nested promises, especially in the recursive case where you increment the value.
I ran into something similar when scraping dynamic content. The issue is that when you call processValue(val++)
recursively, you’re not capturing or returning that promise chain back up to the parent scope. Each recursive call creates its own setTimeout but the results never bubble back up.
Here’s what worked for me in a comparable situation:
const result = await page.evaluate((inputValue) => {
function processValue(val) {
return new Promise((resolve) => {
if (val > 1) {
setTimeout(async () => {
if (val > 8) {
resolve('high');
} else if (val > 3 && val < 8) {
resolve('medium');
} else {
const recursiveResult = await processValue(val + 1);
resolve(recursiveResult);
}
}, 1500);
}
});
}
return processValue(inputValue);
}, value);
The crucial part is awaiting the recursive call and then resolving with that result, ensuring the promise chain stays intact throughout the entire process.
yeah your evaluate function isnt actually returning the promise from processValue. you call processValue but dont return its result, so page.evaluate gets undefined. just add return processValue(inputValue);
at the end of your evaluate callback and make sure processValue returns a promise properly
The core issue is that your processValue
function doesn’t return anything to the outer scope. When you call setTimeout
, it schedules a callback but doesn’t return a promise that page.evaluate can wait for. The function exits immediately without returning a value, hence undefined.
You need to wrap your entire async logic in a Promise and return it from the evaluate function. Something like this approach works:
const result = await page.evaluate((inputValue) => {
return new Promise((resolve) => {
function processValue(val) {
if (val > 1) {
setTimeout(() => {
if (val > 8) {
resolve('high');
} else if (val > 3 && val < 8) {
resolve('medium');
} else {
processValue(val + 1);
}
}, 1500);
}
}
processValue(inputValue);
});
}, value);
The key is explicitly returning a Promise from the evaluate callback and using resolve() to pass your result back when the async operation completes.