Puppeteer define browser and page synchronously - javascript

At the start of the Puppeteer tutorial, it says to do this:
const puppeteer = require('puppeteer');
(async () =>
{
await page.goto('https://example.com');
const browser = await puppeteer.launch();
const page = await browser.newPage();
await browser.close();
})();
This seems odd to me as the whole thing is wrapped inside an asynchronous function. What if I want to wait until this finishes to continue?
Edit - Why this seems odd to me:
What if all my code relied on the browser, i.e., there is nothing I can do outside this async function. Then my code would look like this:
//nothing up here
(async () =>
{
//EVERYTHING is in here
})();
//nothing down here
This seems weird because I might as well do everything synchronously instead of wrapping my entire program in an async function.

Reason for the async function
You need to wrap the code containing await instructions inside an async function for backwards compatibility reasons. Before ES7, you could use the word await as variable or function name, meaning this was valid code:
var await = 123;
console.log(await);
To not mess with existing code, the await keyword only works inside of async functions, meaning to write code like await page.goto(..) you have to put it inside an async function like the one you are using.
Waiting for the code to finish
To wait until the code has finished, you can just continue after the last await statement like this:
(async () => {
// ...
await browser.close();
// continue with more code
})();

Related

Using await promise.all() without async function

I'm trying to make an async function using fetch and then running it in order using await promise.all() and this is the code I'm using(Nodejs18):
let ids = [164838, 793838]
async función download(ids){
//Función code.......
}
await Promise.all(
ids.map(element => download(element))
)
What I'm trying to achieve is that the await promise.all() works without having to put it in an asynchronous function or change the file extension from .js to .mjs
Without changing to modules you can use the previously common method of an immediately invoked function by wrapping you code in a function and then running it.......
let ids = [164838, 793838];
async función download(ids){
//Función code.......
}
(async ()=>{
await Promise.all(
ids.map(element => download(element))
})();
Note the () at the end of the code. That calls the function that has just been created.

Best practice for running a background process in Node?

Say for example I have the following code, a simple UI test.
async function testMyCoolUI() {
await uiFramework.openMyApp(url);
await sleep(2000);
await uiFramework.clickButtonX();
await uiFramework.clickButtonY();
}
Now a new requirement gets added. At any point during the test, a popup could come up on the screen saying "Are you a bot?", and we have to select "No".
How would you structure your test such that this "process" can run constantly in the background of the test, watching for this popup? My initial idea is to just kick off an async function polling for the popup, but don't wait on the promise in testMyCoolUI.
async function testMyCoolUI() {
await uiFramework.openMyApp(url);
await sleep(2000);
startPollingForPopup(); // this is an async function, but not waiting on it
await uiFramework.clickButtonX();
await uiFramework.clickButtonY();
}
However this feels wrong, and the promise will sit unresolved and the process won't clean up nicely. What is the way to do this "correctly" in JS?
Other thought:
Promise.all([testMyCoolUI, pollForPopup]);
But in this case, the test would complete still before the polling ever resolved. And Promise.race doesn't really work here for the same reason.
A good code structuring pattern that will ensure proper cleanup is the promise disposer pattern:
async function testMyCoolUI() {
await uiFramework.openMyApp(url);
await sleep(2000);
await withPollingForPopup(async () => {
await uiFramework.clickButtonX();
await uiFramework.clickButtonY();
});
}
async function withPollingForPopup(run) {
try {
startPollingForPopup(); // not waiting for anything
return await run();
} finally {
stopPollingForPopup(); // optionally `await` it
}
}
This assumes a background process, possibly an event subscription, that can be started and stopped.
Alternatively, if the background process does return a promise which rejects on errors and you want to abort as soon as possible, you might use
async function withPollingForPopup(run) {
const poll = runPollingForPopup();
const [res] = await Promise.all([
run().finally(poll.stop),
poll.promise
]);
return res;
}

Evaluating promise in REPL loop with VSCode and Pupeteer

I'm getting started with puppeteer but have minimal node experience. I'm interested in debugging and trying out pieces of code in a REPL loop. So far I have the following:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: false,
});
const page = await browser.newPage();
await page.goto('https:yahoo.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
I tried to take a second screen shot by entering :
page.screenshot({path: 'example1.png'});
but this returns a promise. Is there a way to evaluate the result within the REPL loop
EDIT:
I entered both lines into the REPL at the bottom of the debug console, the output is in the screenshot. Am I doing something wrong?
EDIT2:
I entered your code into the debug window REPL at the bottom of the debug console, the output is in the screenshot.
If you want to pay with the result in the REPL you'd have to do something like this:
var res; page.screenshot({path: 'example1.png'}).then(r => {res=r;console.log('done')});
The done string is printed you'll have the result in your res variable so you can play with it.
Step by Step:
var res is declaring an empty variable so you can use it later
page.screenshot({path: 'example1.png'}) will return a promise, hence the .then right after it
.then receives a function that will be called asynchronously when the promise is resolved. That function will be called with some input.
r => {res=r;console.log('done')} this is the anonymous function passed to the then of that promise. The argument passed by page.screenshot(...).then will be stored in the r param/variable.
res=r; this sets to the res variable the argument sent from the resolution of the promise.
console.log('done') is just there so you will know when the promise is resolved.
after that, you can inspect the res variable to understand in detail whatever is there.
You can use this approach for ANY type of promise resolution debug, since when in the console (at the moment) we don't have the ability to run something like var res = await page.screenshot({path: 'example1.png'})

Puppeteer: What do the round brackets enclosing an async function mean?

I can't figure out what these round brackets enclosing an async function are for. What are they denoting?
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// more codes here
})();
What you're seeing is called an Immediately Invoked Function Expression. They want to run this code right away, and so there's a pair of () at the end in order to immediately call the function. But just adding those would result in illegal syntax, so in addition the function as a whole needs to be wrapped in parentheses.
If you're curious why they are creating a function just to immediately call it, they're doing it in order to be able to use the await keyword. Await can only be used in an async function, and therefore can't be at the root level of a file. There are other reasons which can motivate the use of an IIFE, but that's the reason in this case.

using await on global scope without async keyword

I am trying to do something like this on global scope in nodejs REPL. As per my understanding both the following statements are valid. see docs
let x = await Promise.resolve(2);
let y = await 2;
However, both these statements are throwing an error.
Can somebody explain why?
my node version is v8.9.4
Update
When using Node, the file currently must have an .mjs extension to work.
Top level awaits can be used in browser modules. When used the script tag must include the type attribute which must be set to module:
<script src="/script.js" type="module"></script>
const start = Date.now()
console.log('Pre call.')
await delayedCall()
console.log('Duration:', Date.now() - start)
function delayedCall() {
return new Promise(resolve => setTimeout(() => resolve(), 2000))
}
Working Node Example -- Run node ./index.mjs in the terminal.
Working Browser Example
Supported Versions
Old Answer
await can only be used within a function that is labeled async, so there are two ways you can approach this.
Note:
There is a proposal in place that may eventually allow the usage of Top level await calls.
The first way is to create a self invoked function like this:
(async function() {
let x = await Promise.resolve(2)
let y = await 2
console.log(x, y)
})()
Or the second way is to use .then()
Promise.resolve(2).then(async data => {
let x = data
let y = await 2
console.log(x, y)
})
This proposal is currently in stage 3 of the TC39 process. LINK
You can use this feature in Google Chrome and Mozilla Firefox as of now. You can use top level await without async in console.
https://twitter.com/addyosmani/status/1080365576218759168
As of version 13.3, Node.js support Top-level await.
Top-level await means you can now use await operator outside an async function. So both examples are correct:
(async function() {
await Promise.resolve(console.log('Hello await!'));
}());
// or
await Promise.resolve(console.log('Hello await!'));
Note: Top-level await only works at the top level of modules. There is no support for classic scripts or non-async functions.
Just keep in mind, that the await operator is used to wait for a Promise. It does NOT matter if you are using an await operator with a value other than a Promise. For example, the name variable in the displayName()` function:
async function displayName() {
const name = await 'unclexo';
console.log(name);
}
displayName(); // outputs 'unclexo'
As the value of the name variable is not a Promise, it converts the value to a resolved Promise, and waits for it. It happens under the hood.
The old behavior
MDN doc says
The await operator is used to wait for a Promise. It can only be used
inside an async function.
since node 10, you can run node process with --experimental-repl-await to allow top level await
https://nodejs.org/api/repl.html#repl_await_keyword
async function getTen() {
return 10;
}
(async () => {
let ten = await getTen();
console.log(ten);
})();
Source: https://javascript.plainenglish.io/5-javascript-interview-questions-to-identify-outstanding-developers-859a71c3d7f
Top-level-await is supported in Node.js version 13.3 or above
Example:
await Promise.resolve(console.log('🎉')); // → 🎉
(Though the question is about REPL, a note on running as script file. Set {"type": "module"} package.json or use the file name extension .mjs)
You could wrap all the code in the global scope in an async function.
For example:
// ...global imports...
new Promise (async () => {
// ...all code in the global scope...
}).then()

Categories

Resources