I'm trying to debug my logic inside of page.waitForFunction but nothing I log out gets printed. If I try to use the VScode debugger it ignores any breakpoints inside the callback function. If I place a break point before the await page.waitForFunction() call and then try to step through it I just end up in /internal/async_hooks and then get stuck through a bunch of node internals. Again if I try to place a breakpoint inside the callback and jump to that, it doesn't stop.
I have no idea what's going on. Am I using it wrong or something? Or completely misunderstanding how it works?
Here's a simple example.
const { chromium } = require("playwright");
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
const i = 0;
await page
.waitForFunction(
(i) => {
i++;
console.log(`---> evaluating # ${i}`);
if (i < 3) return false;
return true;
},
i,
{ timeout: 3000 }
)
.catch(console.error);
console.log("done");
await browser.close();
})();
For this code I expect it to log out
// ---> evaluating #1
// ---> evaluating #2
// done
Instead, nothing from within the callback is logged out and it just logs out
// done
If I always return false in the callback, then it errors with Timeout 3000ms exceeded.Error as expected, so it's like it's working, but I have no way to debug what's going on inside the callback.
A few things there:
First, the function you pass to waitForFunction is being executed inside the browser. So you won't see that log in your app, unless you get the log using the console event.
Second, and related to the previous point. That function will always get 0 in the i argument because i is incremented inside the browser. Which doesn't affect the variable that will be passed over and over.
Related
I am evaluating codeToBeEvaluated function in browser using puppeteer. codeToBeEvaluated has a while loop which is supposed to show an alert (see LINE B) after every 10s.
The problem is when I run this script, I don't see any alerts. Code execution doesn't wait for 10s in Line A. Rather process exits immediately. I have no idea why. I am really curious why it's not working.
Interestingly, when I run only the codeToBeEvaluated function in the browser's console, it works perfectly fine and I see the alerts that I am supposed to see.
It would be awesome if someone could explain this behaviour. I am not looking for any workarounds to show up alerts. I just want to understand this behaviour.
const puppeteer = require("puppeteer");
// this function will be executed in the browser
// it should create an alert after some time interval
const codeToBeEvaluated = async () => {
let index_2 = 0;
// a promise that resolves after ms*1000 seconds
async function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// await within function's while loop
async function whileWrapper() {
while (index_2 < 10) {
await sleep(10000); // LINE A: execution doesn't stop at all :/
alert("hello " + index_2); // LINE B: does NOT execute at all
index_2++;
}
}
whileWrapper();
};
async function main(url) {
const browser = await puppeteer.launch({
// headless: false,
devtools: true,
// args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
await page.goto(url);
await page.evaluate(codeToBeEvaluated);
browser.close();
}
main("https://www.google.com/search?q=hello");
the reason for the difference between executing codeToBeEvaluated on DevTools manually vs. from your puppeteer script is that:
on DevTools console the script has infinite time to execute a longer async command (except if you are quickly closing the browser while it is still running)
in your puppeteer script you have other commands following the page.evaluate like browser.close (which I advise you to put after an await as it returns a promise!), so the browser is closed before the function would finish
you need to await whileWrapper()'s promise too, so changing your code in LINE 25 to the following will make it behave as you would have expected:
await whileWrapper();
I would like to run this code with babel:
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
inside an async function without await the first line. is this OK?
how else can I run something that I don't care?
Can I just fire the non-promisified function del('key',null) without a callback?
Yes, you can do that, and it will run the two asynchronous functions in parallel. You've just created a promise and thrown it away.
However, this means that when the promise is rejected you won't notice. You'll just get an unhandledRejection eventually which will crash your process if not handled.
Is this OK? How can I run something that I don't care?
Probably it's not OK. If you truly wouldn't care, you hadn't run it in the first place. So you should be clear and explicit what you care about (and what not):
do you want to wait? (for side effects)
do you need the result?
do you want to catch exceptions?
If you only want to wait and don't care for the result value, you can easily throw away the result:
void (await someAsyncFunction()); // or omit the void keyword,
// doesn't make a difference in an expression statement
If you don't care about exceptions, you can ignore them using
… someAsyncFunction().catch(function ignore() {}) …
You can throw that away, await it, do anything with it.
If you want the result, you have to await it. If you care about exceptions, but don't really want to wait, you may want to execute it in parallel with the following functions:
var [_, res] = await Promise.all([
someAsyncFunction(), // result is ignored, exceptions aren't
someOtherAsyncFunction()
]);
return res;
inside an async function without await the first line. is this OK?
Yes, there are cases where you'd want to do this which are perfectly reasonable. Especially where you don't care about the result - one example is an analytics tracking operation that should not interfere with business critical code.
how else can I run something that I don't care?
In many ways, however simply calling the promise function works. Your del without a callback would probably work in this case but some functions don't guard against not passing callbacks, so you can pass an empty function instead (.del('key', () => {})).
You do want to however make sure that you know about it failing, even if you don't want to disrupt the operation of code - so please consider adding a process.on("unhandledRejection', event handler to explicitly ignore these particular exceptions or suppress them via:
redisClient.delAsync('key').catch(()=>{});
Or preferably, something like:
redisClient.delAsync('key').catch(logErr);
From all the research I've made so far, I think it's fine to do it, as long as you guarantee that the function you are not awaiting for guarantees a way to handle its own errors in case that happens. For example, a try-catch wrapping the whole function body, like you see in the following snippet for the asyncFunction.
It doesn't matter if the function throws synchronously or asynchronously. It guarantees the your mainFunction will complete no matter what. That's the key point here.
If you don't guarantee that, you have to risks:
If it throws synchronously, your main function will not complete.
If it throws asynchronously, you'll get an unhandled excepction
// THIS IS SOME API CALL YOU DON'T WANT TO WAIT FOR
const mockAPI = () => {
console.log("From mockAPI");
return new Promise((resolve,reject) => {
setTimeout(() => reject("LATE THROW: API ERROR"), 500);
});
};
// THIS IS THE SOME ASYNC FUNCTION YOU CALL BUT NOT AWAIT FOR
const asyncFunction = async (syncThrow) => {
try {
console.log("Async function START");
if (syncThrow) throw new Error("EARLY THROW");
await mockAPI();
console.log("Async function DONE");
}
catch(err) {
console.log("From async function catch");
console.log(err.message || err);
return;
}
};
// THIS IS YOUR MAIN FUNCTION
const mainFunction = async (syncThrow) => {
try {
console.clear();
console.log("Main function START");
asyncFunction(syncThrow);
console.log("Main function DONE <<< THAT'S THE IMPORTANT PART");
}
catch(err) {
console.log("THIS WILL NEVER HAPPEN");
console.log(err);
}
};
<div>
<button onClick="mainFunction(true)">Sync throw</button>
<button onClick="mainFunction(false)">Async throw</button>
</div>
Not in Node.js.
Node does not wait for ever-pending Promises. If other tasks are already completed and there is nothing left in the event loop, the Node process will be terminated even though there exists pending promise.
For the following script, if someOtherAsyncFunction() get resolved in 5 seconds, but redisClientAsync.delAsync('key') takes 10 seconds to execute, the Node process will be terminated after 5 seconds in theory, before the first line is resolved.
async function doSomething() {
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
}
await doSomething();
This PromiseRaceWithIndex() function returns an [index, promise] pair. (C.f. Promise.race() which returns only the promise without the index). This code works perfectly.
async function PromiseRaceWithIndex(ap){
async function snooze(ms){
return new Promise(r=>{setTimeout(r,ms);});
}
await Promise.race(ap);
for (let i=0; i<ap.length; i++){
if (!(ap[i] instanceof Promise))
return [i,ap[i]];
else {
// without using square brackets around the return values
// (and correspondingly around test) the program silently crashes
let [test]=await Promise.race([
(async ()=>{ return [await ap[i]];})(),
(async ()=>{ await snooze(0); return [ap[i]];})(),
]);
if (!(test instanceof Promise))
return [i,test];
}
}
}
(This perfectly working code is demonstrated in JSFiddle. On that page press "run" and then click 'console' in bottom right quad to see the output.)
However, the code can be changed from perfectly working code to buggy code if the following lines are substituted in -
let test=await Promise.race([
(async ()=>{ return await ap[i];})(),
(async ()=>{ await snooze(0); return ap[i];})(),
]);
i.e., if the square brackets are removed from around [test], [await ap[i]], and ap[i]. Then program will silently exit the first time it enters the call to Promise.race([...]). This phenomena is confirmed both in JSFiddle and running under Node 12LTS in Linux.
Certainly it is "buggy" to exit suddenly without an error message. However, as a separate issue, is there a logical reason to terminate processing at that point?.
The only difference I can see is that by returning their results in single element arrays, the promises will not be "re-wrapped" as they are passed out through the arrow functions. But I can't see any reason why that should be a problem.
/**
* #type {<T>(promises: Promise<T>[]) => Promise<{ value: T, i: number }>}
*/
function racePromisesIndex(promises) {
return Promise.race(promises.map((promise, i) => promise.then(value => ({ value, i }))))
}
here's much simpler race promises with index function in case anyone wants it
As noted by #Ouroborus in the comments to the original post
Without the brackets, the second entry returns a promise which continues the race.
As for why it doesn't hang, but exits instead, it is because the event queue has emptied (when run in node from a terminal). This can be confirmed by adding the following line to end of the program
process.on('beforeExit',(code)=>{console.log(`beforeExit ${code}`);});
(Note: doesn't work on the JSFiddle page because the process variable is not exposed.)
The beforeExit event is documented here.
I would like to run this code with babel:
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
inside an async function without await the first line. is this OK?
how else can I run something that I don't care?
Can I just fire the non-promisified function del('key',null) without a callback?
Yes, you can do that, and it will run the two asynchronous functions in parallel. You've just created a promise and thrown it away.
However, this means that when the promise is rejected you won't notice. You'll just get an unhandledRejection eventually which will crash your process if not handled.
Is this OK? How can I run something that I don't care?
Probably it's not OK. If you truly wouldn't care, you hadn't run it in the first place. So you should be clear and explicit what you care about (and what not):
do you want to wait? (for side effects)
do you need the result?
do you want to catch exceptions?
If you only want to wait and don't care for the result value, you can easily throw away the result:
void (await someAsyncFunction()); // or omit the void keyword,
// doesn't make a difference in an expression statement
If you don't care about exceptions, you can ignore them using
… someAsyncFunction().catch(function ignore() {}) …
You can throw that away, await it, do anything with it.
If you want the result, you have to await it. If you care about exceptions, but don't really want to wait, you may want to execute it in parallel with the following functions:
var [_, res] = await Promise.all([
someAsyncFunction(), // result is ignored, exceptions aren't
someOtherAsyncFunction()
]);
return res;
inside an async function without await the first line. is this OK?
Yes, there are cases where you'd want to do this which are perfectly reasonable. Especially where you don't care about the result - one example is an analytics tracking operation that should not interfere with business critical code.
how else can I run something that I don't care?
In many ways, however simply calling the promise function works. Your del without a callback would probably work in this case but some functions don't guard against not passing callbacks, so you can pass an empty function instead (.del('key', () => {})).
You do want to however make sure that you know about it failing, even if you don't want to disrupt the operation of code - so please consider adding a process.on("unhandledRejection', event handler to explicitly ignore these particular exceptions or suppress them via:
redisClient.delAsync('key').catch(()=>{});
Or preferably, something like:
redisClient.delAsync('key').catch(logErr);
From all the research I've made so far, I think it's fine to do it, as long as you guarantee that the function you are not awaiting for guarantees a way to handle its own errors in case that happens. For example, a try-catch wrapping the whole function body, like you see in the following snippet for the asyncFunction.
It doesn't matter if the function throws synchronously or asynchronously. It guarantees the your mainFunction will complete no matter what. That's the key point here.
If you don't guarantee that, you have to risks:
If it throws synchronously, your main function will not complete.
If it throws asynchronously, you'll get an unhandled excepction
// THIS IS SOME API CALL YOU DON'T WANT TO WAIT FOR
const mockAPI = () => {
console.log("From mockAPI");
return new Promise((resolve,reject) => {
setTimeout(() => reject("LATE THROW: API ERROR"), 500);
});
};
// THIS IS THE SOME ASYNC FUNCTION YOU CALL BUT NOT AWAIT FOR
const asyncFunction = async (syncThrow) => {
try {
console.log("Async function START");
if (syncThrow) throw new Error("EARLY THROW");
await mockAPI();
console.log("Async function DONE");
}
catch(err) {
console.log("From async function catch");
console.log(err.message || err);
return;
}
};
// THIS IS YOUR MAIN FUNCTION
const mainFunction = async (syncThrow) => {
try {
console.clear();
console.log("Main function START");
asyncFunction(syncThrow);
console.log("Main function DONE <<< THAT'S THE IMPORTANT PART");
}
catch(err) {
console.log("THIS WILL NEVER HAPPEN");
console.log(err);
}
};
<div>
<button onClick="mainFunction(true)">Sync throw</button>
<button onClick="mainFunction(false)">Async throw</button>
</div>
Not in Node.js.
Node does not wait for ever-pending Promises. If other tasks are already completed and there is nothing left in the event loop, the Node process will be terminated even though there exists pending promise.
For the following script, if someOtherAsyncFunction() get resolved in 5 seconds, but redisClientAsync.delAsync('key') takes 10 seconds to execute, the Node process will be terminated after 5 seconds in theory, before the first line is resolved.
async function doSomething() {
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
}
await doSomething();
I would like to run this code with babel:
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
inside an async function without await the first line. is this OK?
how else can I run something that I don't care?
Can I just fire the non-promisified function del('key',null) without a callback?
Yes, you can do that, and it will run the two asynchronous functions in parallel. You've just created a promise and thrown it away.
However, this means that when the promise is rejected you won't notice. You'll just get an unhandledRejection eventually which will crash your process if not handled.
Is this OK? How can I run something that I don't care?
Probably it's not OK. If you truly wouldn't care, you hadn't run it in the first place. So you should be clear and explicit what you care about (and what not):
do you want to wait? (for side effects)
do you need the result?
do you want to catch exceptions?
If you only want to wait and don't care for the result value, you can easily throw away the result:
void (await someAsyncFunction()); // or omit the void keyword,
// doesn't make a difference in an expression statement
If you don't care about exceptions, you can ignore them using
… someAsyncFunction().catch(function ignore() {}) …
You can throw that away, await it, do anything with it.
If you want the result, you have to await it. If you care about exceptions, but don't really want to wait, you may want to execute it in parallel with the following functions:
var [_, res] = await Promise.all([
someAsyncFunction(), // result is ignored, exceptions aren't
someOtherAsyncFunction()
]);
return res;
inside an async function without await the first line. is this OK?
Yes, there are cases where you'd want to do this which are perfectly reasonable. Especially where you don't care about the result - one example is an analytics tracking operation that should not interfere with business critical code.
how else can I run something that I don't care?
In many ways, however simply calling the promise function works. Your del without a callback would probably work in this case but some functions don't guard against not passing callbacks, so you can pass an empty function instead (.del('key', () => {})).
You do want to however make sure that you know about it failing, even if you don't want to disrupt the operation of code - so please consider adding a process.on("unhandledRejection', event handler to explicitly ignore these particular exceptions or suppress them via:
redisClient.delAsync('key').catch(()=>{});
Or preferably, something like:
redisClient.delAsync('key').catch(logErr);
From all the research I've made so far, I think it's fine to do it, as long as you guarantee that the function you are not awaiting for guarantees a way to handle its own errors in case that happens. For example, a try-catch wrapping the whole function body, like you see in the following snippet for the asyncFunction.
It doesn't matter if the function throws synchronously or asynchronously. It guarantees the your mainFunction will complete no matter what. That's the key point here.
If you don't guarantee that, you have to risks:
If it throws synchronously, your main function will not complete.
If it throws asynchronously, you'll get an unhandled excepction
// THIS IS SOME API CALL YOU DON'T WANT TO WAIT FOR
const mockAPI = () => {
console.log("From mockAPI");
return new Promise((resolve,reject) => {
setTimeout(() => reject("LATE THROW: API ERROR"), 500);
});
};
// THIS IS THE SOME ASYNC FUNCTION YOU CALL BUT NOT AWAIT FOR
const asyncFunction = async (syncThrow) => {
try {
console.log("Async function START");
if (syncThrow) throw new Error("EARLY THROW");
await mockAPI();
console.log("Async function DONE");
}
catch(err) {
console.log("From async function catch");
console.log(err.message || err);
return;
}
};
// THIS IS YOUR MAIN FUNCTION
const mainFunction = async (syncThrow) => {
try {
console.clear();
console.log("Main function START");
asyncFunction(syncThrow);
console.log("Main function DONE <<< THAT'S THE IMPORTANT PART");
}
catch(err) {
console.log("THIS WILL NEVER HAPPEN");
console.log(err);
}
};
<div>
<button onClick="mainFunction(true)">Sync throw</button>
<button onClick="mainFunction(false)">Async throw</button>
</div>
Not in Node.js.
Node does not wait for ever-pending Promises. If other tasks are already completed and there is nothing left in the event loop, the Node process will be terminated even though there exists pending promise.
For the following script, if someOtherAsyncFunction() get resolved in 5 seconds, but redisClientAsync.delAsync('key') takes 10 seconds to execute, the Node process will be terminated after 5 seconds in theory, before the first line is resolved.
async function doSomething() {
redisClientAsync.delAsync('key');
return await someOtherAsyncFunction();
}
await doSomething();