I have two functions that I want to run one after the other finishes. I'm using webdriver.IO so I need to wait for one function to log into the page, and then I want another function to run.
Other questions here on SOF are telling me to use a promise, however I need to use a promise, however when I wrap my functions in a promise I get the error SyntaxError: await is only valid in async function.
I have a login function
const Login = async () => {
const browser = await remote({
capabilities: {
browserName: 'chrome'
}
})
const userName = await browser.$('#username')
const password = await browser.$('#password')
await userName.setValue(process.env.NAMEUSERNAME)
await password.setValue(process.env.PASSWORD)
const loginButton = await browser.$('button[type="submit"]')
await loginButton.click()
}
Once this completes and the page loads I want to run another function.
I have gotten this to work with a setTimeout however I don't want to just have a chain of setTimeout in my application
It's hard to give you precise code without seeing where this is called from: but presuming it's inside some other function, you just have to mark that one async and use await inside it:
async theOuterFunction() {
await Login();
doTheOtherThing();
}
(If doTheOtherThing also returns a promise you could await that too - and would need to if you want to run other code inside theOuterFunction after that. But there's no need if it's the last statement inside the function.)
Related
I am using axios (npm module) to send a request to my server. This routine is part of a separate Node.js process I start, which should automatically close after its work is done.
However, I seem to be misinterpreting something about the promise. Consider the function I use to log these requests:
async log(request) {
return await axios.post('example.com', {
screenshot: await page.screenshot({type: 'png', encoding: 'base64'}),
});
}
I push all these calls to an array, so I know when to close the process:
this.awaitPromises.push(this.log(request));
later on in the code I call:
await Promise.all(this.awaitPromises);
As you can see there is a nested await page.screenshot(...) inside the parameters object of the axios call which creates a screenshot of the browser (puppeteer). This log(request) code however gives me an error indicating that the browser has closed unexpectedly: Protocol error (Page.captureScreenshot): Target closed.. This means that the await Promise.all() has resolved to soon because after that I close the browser.
The code does work when I add an extra .then() to the log function like so:
async log(request) {
return await axios.post('example.com', {
screenshot: await page.screenshot({type: 'png', encoding: 'base64'}),
}).then(() => {
// Do nothing.
});
}
The code now works as intended and the browser is only closed after the log function has (completely) been completed.
Anyone knows what the difference is here and why the last example works? It must have something to do with the inner await page.screenshot right?
I've been trying to learn the JS concurrency model given a background in Python's.
Running the following in Python:
async def myFunction():
print("abc")
myFunction()
will print nothing. Running the following in JavaScript:
async function myFunction() {
console.log("abc")
}
myFunction()
will print "abc". In both cases I did not await myFunction, yet the evaluations were different.
I understand why the Python program prints nothing. myFunction is a coroutine function that resolves to nothing, more specifically it returns an Awaitable[None]. But to actually have this awaitable's side effect executed, I must await it.
I have also read Are promises lazily evaluated? with an answer of no, talking about how the eager evaluation of promises is guaranteed.
Even though I've looked at both concurrency models separately, their difference is still confusing. While general clarity about the contrast here would be very helpful, I also do have a specific question: Is there ever a point to awaiting a Promise in JavaScript that resolves to nothing, and should only execute for its side effect? In other words, do await myFunction() and myFunction() possibly have a difference in behavior in a different case, even though they both gave the same output here?
async def myFunction():
print("abc")
awaitable = myFunction()
In Python, myFunction() returns an awaitable. The code hasn't run yet. It only runs when you execute a method (.send()) on the awaitable:
awaitable.send(None)
You typically don't use .send(), these mechanics are hidden. You will just use await myFunction() or asyncio.get_event_loop().run_until_complete(myFunction()), and this will execute the function's code behind the scenes.
In JavaScript, an async function returns a promise. The MDN JS reference says:
Promises in JavaScript represent processes that are already happening
So when you call the async function in JavaScript, the function's code starts running, and at some point a promise is returned. Here's what the MDN JS reference says about it:
The body of an async function can be thought of as being split by zero or more await expressions. Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.
Given all this, I don't see why you would await an async JS function if you only wanted its side effect (unless the caller needs the side effect to have occurred before proceeding).
Inside an async function all the asynchronous operations are done by using an await in front of it.
If there is an await in front of a promise, then the we wait until the async operation is completed and then continue the remaining code execution.
If we don't use await infront of an async operation, we don't resolve the promise and continue.
Await in front of a synchronous operation has no effect.
// Case I
const test = async() => {
let res = fetch("http://localhost:3000");
console.log(res);
console.log("It is working");
// We dont wait for the http request to complete,
// We print res as a <<promise>>
// and continue to the next line and print "It is working".
}
test();
// Case II
const test = async() => {
let res = await fetch("http://localhost:3000");
console.log(res);
console.log("It is working");
// We wait for the http request completion.
// After that we print the result.
// Then print ===> It is working
}
test();
()=> {} // stands for an anonymous function also know as Fat arrow function.
You use await in front of a function only if it returns a promise.
If it returns a promise, we wait for the promise to resolve and continue code execution.
If it doesn't returns a promise, default behavior is expected.
const test = () => {
return fetch("http://localhost:3000");
}
let testingAsynFunc = async() => {
let a = await test();
//Here we wait until the promise is resolved and then continue the code execution.
console.log(a);
console.log("working");
}
testingAsyncFunc()
I write the following code to read the data from the file and push into array. But attachments.length is printing 0 first and then data is loaded and print.
const fs=require('fs');
const util=require('util');
const files=['t1.csv','t2.csv','t3.csv'];
async getdata(){
const read=util.promisify(fs.readFile);
let attachments = [];
async function run(file){
let data=await read(file);
attachments.push(data);
}
for(let file of files){
await run(file);
}
console.log(attachments.length);//should print 3
}
How to load the data first and then push correctly.
Edit: change the some part of code to use await. But loop break after first iteration without giving any error neither print my attchments.length .
Edit 2: problem resolved. Calling function should also need to be await. Thanks every one.
This is happening because run() should be awaited as well in this case,
see async function
One approach is using IIFE:
(async file => {
let data = await read(file);
console.log(data);
attachments.push(data);
})('/home/nadeem/Desktop/test.csv')
When you call an async function, it returns a promise that will eventually resolve with the value returned by the function (or reject with an uncaught exception thrown from within the function).
So you're trying to log the number of attachments before the run () function has finished. Here's what you need:
run('/home/nadeem/Desktop/test.csv')
.then(() => console.log(attachments.length))
The module-scope variable "output" refuses to be overwritten by the async function "retrieveTextWrapper", and I cannot figure out why. My objective is to output the text on StackOverFlow's homepage. retrieveTextWrapper successfully scrapes this information, but I can't seem to assign this content to the output variable. What am I doing wrong? How can I print the scraped information from the main() function?
Note: I am using electron version 3.0.4 because bypassing CORS is less of a pain on that version.
const {BrowserWindow, app} = require('electron')
output = "this should be overwritten by the retrieveTextWrapper method"
async function main(){
navigate();
win.openDevTools();
await win.webContents.once('dom-ready',retrieveTextWrapper);
console.log(output);
//prints "this should be overwritten by the retrieveTextWrapper method"
}
function navigate() {
win = new BrowserWindow({width:900,height:900});
win.loadURL(`https://stackoverflow.com/`);
}
function retrieveText(){
return `document.querySelector("*").innerText`;
}
async function retrieveTextWrapper(){
output = await win.webContents.executeJavaScript(retrieveText().replace("*", "#content"));
}
app.on('ready',main)
win.webContents.once() does not return a promise (since interfaces generally don't accept both callbacks and return a promise at the same time).
Therefore await doesn't wait for the asynchronous operation to complete. Therefore, you're looking at output before its value has been reassigned. await only does something useful when you await a promise that is connected to the asynchronous operation you're trying to wait for.
To confirm this timing issue, add a unique console.log() statement before and after the await win.webContents.once('dom-ready',retrieveTextWrapper); and inside of retrieveTextWrapper and then you can see the sequencing of these log messages.
Yep, everything changes as it should within retrieveTextWrapper function. And your explanation makes a lot of sense. However, is it possible to wait for the callback to finish (using some other syntax aside from await)? That way, I can use the updated value for other operations in the main function?
You have a couple options.
You could "promisify" win.webContents.once() so you could then use await with it.
You could put the callback inline and put the rest of your code in main inside that callback (a classic way of dealing with asynchronous operations).
Here's an example of promisifying win.webContents.once():
function waitForDomReady() {
return new Promise((resolve, reject) => {
// may want to check if document already has dom-ready and resolve immediately
win.webContents.once('dom-ready', resolve);
});
}
And, you could then use it like this:
async function main(){
navigate();
win.openDevTools();
await waitForDomReady();
await retrieveTextWrapper();
console.log(output);
}
This assumes that the code in retrieveTextWrapper that calls win.webContents.executeJavaScript() does actually return a promise when it's done. If not, you have to promisify that too.
I'm trying to refactor my tests using Selenium webdriver and Mocha to ES7 with async/await functionality. I have got following piece of code:
await loginPage.loginAsAdmin()
/* THIS DOES NOT WORK */
//await layout.Elements.routePageButton().click()
/* THIS DOES WORK */
let a = await layout.Elements.routePageButton()
await a.click()
I don't understand why the particular does not work - I get:
TypeError: layout.Elements.routePageButton(...).click is not a function
Function before click method returns webElement, as you can see:
Layout:
routePageButton: async () => await findVisibleElement('#route_info a')
const findVisibleElement = utils.Methods.Element.findVisible
Method:
findVisible: async (cssSelector) => {
let elm = await driver().findElement(by.css(cssSelector))
return elm
}
The problem here is misunderstanding that await is a language keyword in ES2017 that allows you to block execution of the calling async function until a Promise returned by an invoked function resolves.
routePageButton() returns a Promise, and this is why the second syntax above works, as execution is blocked until the Promise resolves to a WebElement object.
However in the syntax you are using in the first example, the function that it is attempting to await on (click()) is never called, because a Promise does not have a click() function. Note that you have two awaits in your second syntax, but only one in your first.
To do what you are attempting to do in one line, you would have to do something like:
await (await layout.Elements.routePageButton()).click()