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

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.

Related

Because I can't run await on the top level, I have to put it into an async function - why can I then call that async function directly?

I have a short Node.js script where I require another package and call an async function from it and subsequently want to print the return value. If I simply await the return value from the top level, then I'll get an error, saying that I can only use await inside an async function itself. So apparently the way to go is like this:
async function main() {
foo = await someOtherAsyncFunc();
console.log(foo);
}
main()
Or:
(async function() {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
Or:
(async () => {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
(Credit to VLAZ in chat https://chat.stackoverflow.com/transcript/message/54186176#54186176)
This works - but I want to understand the reasons behind it a little bit more: I'm used to not being able to directly use await from the top level. However, I'm also used to having to call some special library function to actually "venture" into async from the top level. In Python, see asyncio.run for example. What's the point of requiring await to be inside an async function - if I can then call just any async function from the top level? Why then isn't await available at top level, too?
Top-level await used to not be a thing, but it is possible now in ES6 modules.
One reason why top-level await used to not be a thing, and is still not a thing outside of modules is that it could permit syntactical ambiguity. Async and await are valid variable names. outside of modules. If a non-module script permitted top-level await, then, short of re-working the specification (and breaking backwards compatibility), there would be circumstances when the parser couldn't determine whether a particular instance of await was a variable name, or was used as the syntax to wait for the Promise on its right-hand side to resolve.
To avoid any possibility of ambiguity, the parser, when parsing a section of code, essentially needs to have flags that indicate whether await is valid as an identifier at any given point, or whether it's valid as async syntax, and those two must never intersect.
Module scrips permit top-level await (now) because the use of await as an identifier has always been forbidden in them, so there is no syntactical ambiguity.
In contrast, there are zero issues with using .then on the top level because it doesn't result in any ambiguity in any circumstances.
Why doesn't it just return a Promise which is never executed because it doesn't get awaited?
Promises aren't really "executed". They can be constructed, or waited on to fulfill, or waited on to reject. If you have a Promise, you already have some ongoing code that will (probably) eventually result in a fulfillment value being assigned to the Promise.
Hanging Promises are syntactically permitted - values that resolve to Promises but which aren't interacted with elsewhere. (Which makes sense - every .then or .catch produces a new Promise. If every Promise had to be used by something else, you'd end up with an infinite regress.)
Doing
(async () => {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
is essentially syntax sugar for
someOtherAsyncFunc()
.then((foo) => {
console.log(foo);
});
There's no need to tack anything else onto the end of either of those. (though it's recommended to add a .catch to a dangling Promise so unhandled rejections don't occur)

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

Global function declared in background page does not get called via evaluateHandle

I have a global function in background page like so:
window.myfn = function(){
return new Promise((resolve, reject) => { stuff; });
};
I am using Jest and Puppeteer. Consider this in my test.js file:
async function getBackgroundPage() {
const targets = await browser.targets(),
backgroundPageTarget = targets.find(
target => target.type() === "background_page",
),
backgroundPage = await backgroundPageTarget.page();
return backgroundPage;
}
async function sendUpdate() {
const bgPage = await getBackgroundPage(),
argString = `() => window.myfn()`;
await bgPage.evaluateHandle(argString);
await sleep(30000);
}
getBackgroundPage is literally copied from the docs, so I hope it's correct. However, sendUpdate() does not work as expected. Ideally, window.myfn should be called but actually it is never called. I know this because:
I put several console.logs in it, so if it was called there should have been some log output.
The 30second sleep gives me enough time to goto chrome://extensions, and open the background page console via Inspect Views, and I could not find any log outputs there.
There are no errors in both the background page, and the Jest console. What is the problem then? Why is that global function not being called?
There is a minor difference between giving a function and giving a string to the evaluateHandle (and evaluate) function.
If you pass a function, the function will be called inside the page. If you pass a string, the string will be executed in the page context. This means, that these two lines do different things:
await bgPage.evaluate(() => console.log("test"););
await bgPage.evaluate('() => console.log("test");');
The first line will execute the function (and run console.log) inside the page. However, the second line will only declare the function and not call it. Therefore, if we pass a string we have to do one of these things:
await bgPage.evaluate('console.log("test")'); // directly run console.log
await bgPage.evaluate('(() => console.log("test"))()'); // execute the function
Fixing your code
Coming back to your code, this means you either have directly call window.myfn() or you pass the argument as a function:
await bgPage.evaluateHandle('window.myfn()'); // directly call the function
await bgPage.evaluateHandle(() => window.myfn()); // or pass as a function and not as string

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

Why is `async` not a a reserved word?

As far as I can tell, both the spec and the documentation have await as the only reserved keyword out of the async/await feature.
This is further demonstrated by the fact that we can name a variable async:
For example:
var async = 5;
console.log(async) // this is fine
Node (6.10) (also on Repl.it)
Chrome (59)
Firefox (54)
Is it because of backwards compatibility? I'd guess many codebases would use the name async for certain features.
This allows for some strange looking code examples:
async function async() {
var async = 5;
await async;
return async;
}
async().then(console.log)
Infinite recursive promise chain? (Not really important since any function name would allow this, however this code looks additionally confusing)
async function async() {
await async();
}
// stackoverflow (might need to open your console to see the output)
async does not need to be a reserved word, because it can be uniquely identified. The contexts in which it can occur are those such as
async function() { }
async () => { }
obj = { async foo() { } };
All of these could not be parsed in any way other than seeing async as indicating an async function.
On the other hand, await can in theory be used in a statement such as
async function foo() {
await(1);
}
which is ambiguous; is await awaiting the value 1, or is it a function being called with the parameter 1? Therefore, await needs to be a reserved word (inside async functions; outside, feel free to use it as a variable).
Remember that JavaScript has evolved greatly since its origin. Many words were designated as reserved, and then never used, or designated as reserved when technically they might not need to have been. The designation of await as a reserved word (within modules), and the non-designation of async as a reserved word, are the product of a more mature understanding of the language on the part of its designers.

Categories

Resources