Reputation: 1359
I'm playing with Puppeteer and wrote this example (should probably never happen in production, but still):
const puppeteer = require('puppeteer');
(async () => {
// creating browser instance and closing it
const browser = await puppeteer.launch({ headless: false })
browser.disconnect()
await browser.close()
console.log('first check') // everything is ok here, message is printed
// opening page on the closed browser instance. Just for testing purposes
const page = await browser.newPage()
await page.goto('http://google.com')
// never printed
console.log('second check')
})()
So basically, I am trying to create a new page on a closed instance of the browser. Obviously, no page is opening because browser instance is closed. But I am expecting some error. Instead nothing happens and the second console.log
is never executed!.
Question. If no error is thrown, why does the program never reach the second console.log
? Does puppeteer somehow closes the process of my NodeJS application? Or I am missing something?
puppeteer version: latest - 5.3.1 (also 3.0.0)
By the way, if I use some earlier puppeteer version (2.0.0), same code is failing with error as I expect:
Error: WebSocket is not open: readyState 2 (CLOSING)
Update.
After debugging a bit the internals of Puppeteer I found out the following:
They have a Connection
class with the map of callbacks as a property. Whenever we call the newPage
method, a connection with new id is created as well as a new corresponding Promise
. This promise resolve
and reject
functions are assigned to the callbacks
map:
send(method, ...paramArgs) {
const params = paramArgs.length ? paramArgs[0] : undefined;
const id = this._rawSend({ method, params });
return new Promise((resolve, reject) => {
this._callbacks.set(id, { resolve, reject, error: new Error(), method });
});
}
Then, the Connection
class has the _onMessage(message)
callback. Whenever some data (message) is received, they inspect the message to find out if it is an OK
or an ERROR
message. After this they invoke the stored resolve
or reject
callback.
But since the browser instance is my example is already closed, the message never arrives and the Promise
is neither resolved
nor rejected
.
And after small research, I found out that NodeJS is not able to track such a Promises
. Example:
(async () => {
const promise = new Promise((resolve, reject) => {
if (true === false) {
resolve(13) // this will never happen
}
})
const value = await promise
console.log(value) // we never come here
})()
Upvotes: 1
Views: 2644
Reputation: 53
I agree that this seems to be a bug. I see the issue you made and added a potential fix.
Adding this as the first thing in Connection.send() seems to fix the issue:
if (this._closed)
return Promise.reject(new Error(`Protocol error (${method}): Target closed.`));
In the mean time, I have added this to my code so at least it doesn't die silently with no indication that it failed:
process.on('beforeExit', (code) => {
//beforeExit will run if out of callbacks, but not on an exit()
console.log('We seem to be exiting purely because there are no more awaits scheduled instead of having reached and exit. Assuming this is bad behavior from the browser process. previous exit code: ', code);
process.exit(1);
});
//my code goes here
asdf()
process.exit(0);//will exit without triggering the beforeExit message.
Honestly the behavior of Node in silently exiting seems like it is a little lacking. You can set an exitCode, but having a program completely able to run up to an await then die silently without triggering exception handlers or finally blocks is a little gross.
Upvotes: 2
Reputation: 16450
You don't see any error probably because you don't wait for the async
function to settle. If you attach a catch
handler most likely you'll catch the error:
const puppeteer = require('puppeteer');
(async () => {
// creating browser instance and closing it
const browser = await puppeteer.launch({ headless: false })
browser.disconnect()
await browser.close()
console.log('first check') // everything is ok here, message is printed
// opening page on the closed browser instance. Just for testing purposes
const page = await browser.newPage()
await page.goto('http://google.com')
// never printed
console.log('second check')
})()
.then(() => console.log('done'))
.catch(e => console.error(e)); // <= HERE
Or use try/catch
:
const puppeteer = require("puppeteer");
(async () => {
try {
// creating browser instance and closing it
const browser = await puppeteer.launch({ headless: false });
browser.disconnect();
await browser.close();
console.log("first check"); // everything is ok here, message is printed
// opening page on the closed browser instance. Just for testing purposes
const page = await browser.newPage();
await page.goto("http://google.com");
// never printed
console.log("second check");
} catch (e) {
console.error(e);
}
})();
Upvotes: 0