Evaluating promise in REPL loop with VSCode and Pupeteer - javascript

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'})

Related

Puppeteer define browser and page synchronously

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
})();

Puppeteer: waitForFunction sometimes never resolves

I have simple Puppeteer script:
await page.goto(MY_URL, {waitUntil: 'load'});
const html = await page.evaluate(() => document.body.innerHTML);
Then I check if html contains some key strings and this part always pass (I mentioned in case if this could anyhow influence further process).
Abd after that, I wait for a function to be included in window object.
await page.waitForFunction(() => 'myFunction' in window);
This function is written at the bottom of the script attached in <head> by <script> tag of the page.
Mostly, waitForFunction resolves as it should, but sometimes it doesn't.
And when I pass {timeout: 0} it gonna wait forever and never resolves.
It also seems that this happen only in headless mode.
What could be the cause of such a behavior? How to overcome or debug such issue?
Seems that JavaScript files sometimes weren't loaded.
The solution for me was:
await page.goto(MY_URL, {waitUntil: 'networkidle2'});

Why isn't await working with async on firefox

basically i have this function
async function get(url){
const response = await fetch(url);
const resData = await response.text();
return resData;
}
then later i have this call
let data = await get(am_url);
the code works perfectly on google chrome, but on firefox, i get this error on the call line :
SyntaxError: await is only valid in async functions and async generators
what's the problem here, for the life of me, i can't seem to make this work on firefox and can't figure out why
for example if i open google.com on firefox and google chrome, then i go to the console, and pase this code, on chrome, it will run, but on firefox, it will throw the error i mentionned
async function get(url){
const response = await fetch(url);
const resData = await response.text();
return resData;
}
let data = await get("http://google.com");
console.log(data)
In main either put your below code in self executing async function or use .then.
let data = await get(am_url);
should be changed to
(async()=>{ let data = await get(am_url) })()
or
get(am_url).then( data => ....)
As the error suggests, await only works inside async functions. Normally you can't use await like that, but you have to create an async function first or use .then() instead of await.
However there are 2 things to be aware of:
The console supports await outside an async function just to simplify your life. Chrome did it first, then Firefox more recently. Your example now works in both browsers.
In a future version of ECMAScript, you will be able to use await outside async functions, it's called "top-level await", so that code will soon work everywhere (in a type="module" context)
In 2018 when this question was asked, most JavaScript consoles did not await at the top level.
At that time, Google Chrome Developer Tools console was the exception. They added the feature in late 2017 in Chrome 62
This is why, in the Firefox version you used when you asked this question, you have to resolve the promise, for example with then/catch.
If you update to a current Firefox, such as version 72 (early 2020), the code in your question will work. As fregante pointed out in a comment, in 2019, Firefox upgraded their console to support top level await.

Does awaiting a synchronous function synchronously return a value?

I imported a function from an internal utility library that I am unfamiliar with. There is no documentation for this library and I just assumed it was asynchronous due to its name getUserDetails. I thought it was doing an http request.
I used it in an async function like this
async function getAllUserInfo(event) {
const details = await getUserDetails(event);
// other stuff
}
I was wrong in my assumption. A co-worker pointed out that is was not asynchronous. I ended up changing it, but when I was using it incorrectly it still worked. I was able to await a synchronous function and it returned the correct data.
My question is in regards to how it worked. Does prepending an await on a synchronous function make it resolve on the next tick, or does it return immediately like a synchronous function should?
It worked because await does not require its operand to be a promise! It returns the value of the awaited expression if it is not a promise.
See the documentation for the await operator
The important part is:
[rv] = await expression;
expression: A Promise or any value to wait for.
rv: Returns the fulfilled value of the promise, or the value itself if it's not a Promise.
In your case getUserDetails did not return a promise, but rather some regular user details, so the await expression just returned those details, just as if the operator was not there at all.
However, even though getUserDetails is synchronous, preceding it with await in your async function will give up control to its caller, and the "callback portion" after the await is picked up later. Here is an example script:
function f() {
console.log('In f');
}
async function g() {
console.log('Starting g');
await f();
console.log('Finishing g');
}
console.log('Starting the script')
g();
console.log('Finishing the script')
Notice the output of the script:
$ node asynctest.js
Starting the script
Starting g
In f
Finishing the script
Finishing g
Notice how the await call "paused" g, which was not able to resume until the main block finished! So the await did have an effect. If you did not put the await there, then you would have seen "Finishing g" before "Finishing the script". Try it out!
BTW the reason for the effect is that even though await can be given an expression that does not produce a promise, JS will turn a non-promise operand into a promise immediately resolved to that value. So a promise is still created and the part after await is treated as a callback, which cannot be run until the current execution flow finishes.
If you await a value that is not a promise, it is converted to a resolved promise by using Promise.resolve.
function sync(){
return 1
}
(async ()=>{
const v = await sync(); console.log(v)
})();
(async ()=>{
const v = await Promise.resolve(sync()); console.log(v)
})()

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