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
Related
I am trying to get the url as a string in Cypress. Here is my code:
let schoolDistrictViewLink1 = null;
cy.url().then(($url) => { schoolDistrictViewLink1 = $url, cy.log($url)})
cy.log(schoolDistrictViewLink1)
When I run the test, the first log properly logs the url, but schoolDistrictViewLink1 is still null.
I cannot get schoolDistrictViewLink1 to equal the url.
This will not work because of the asynchronous nature of Cypress. Cypress themselves advises to use aliases to save values for later use.
https://docs.cypress.io/guides/core-concepts/variables-and-aliases
In you example you can put the value of cy.url() in an alias. Which you can get via cy.get('#aliasName') later:
Example:
cy.url().as("schoolDistrictViewLink1");
// do other things
// when you need to use the value
cy.get("#schoolDistrictViewLink1").then((url) => {
cy.log(url);
});
If you are looking to chain commands immediately to cy.url then you will not have to do anything just chain.
cy.url()
.should('eq', 'https://www.website.com/')
If you want to use it at a later point in your test you can use a combination of function() and this keyword
let url
it('check url', function(){
cy.url().then( u => url = u)
// later in your test
cy.get('selector').should('have.text', this.url)
})
Explanation of the problem:
you are trying to assign the value of the URL to schoolDistrictViewLink1 in a callback function passed to cy.url(). This means that the value of schoolDistrictViewLink1 will not be updated until the callback function is called, which happens asynchronously after cy.url() is executed.
Solution:
you can use the async/await pattern to wait for the value to be updated.
async function getUrl() {
let schoolDistrictViewLink1 = null;
await cy.url().then(($url) => { schoolDistrictViewLink1 = $url, cy.log($url)});
return schoolDistrictViewLink1;
}
const url = await getUrl();
cy.log(url);
Happy coding !
I am trying to block content in a react-native web view using the onShouldStartLoadWithRequest function. I use async functions for the engine that decides whether or not to block a specific request. Since I am using an async function to load the engine and need to return a boolean to the onShouldStartLoadWithRequest function, I ended up using the .then method to pass the engine variable to my function that uses it. However, when I test this out, no requests are getting blocked. I tried logging the code ran in the .then method in the console to see what is going on and I did end up getting a boolean. However, when I tried another random async function that always returns false(it will always block content), that didn't work either(with a normal function it did work), so I feel like I am not handling the async functions correctly somehow. Here is my applicable code. In the end I am trying to return a boolean, is that not what is happening in the onShouldStartLoadWithRequest function?
//This is my loading function - it is located outside of the onShouldStartLoadWithRequest function
async function loadEngine() {
let engine = await FiltersEngine.fromPrebuiltAdsAndTracking(fetch);
return engine
}
//This is my onShouldStartLoadWithRequest function
onShouldStartLoadWithRequest={(WebViewRequest) => {
function useEngine(requestURLS, engine) {
const {
match
} = engine.match(Request.fromRawDetails({
url: requestURLS,
}))
let final_output;
final_output = !match
return final_output
}
return loadEngine().then((engine) => (useEngine(WebViewRequest.url, engine)))
}}
[Old question] How can you know if your function concumer used await on invoking your function or not:
Maybe some magic like this:
function x(){
return new Promise((resolve, reject)=>{
console.log(x.awaitWasCalled) // true
})
}
const a = await x()
I'm asking about that because I've seen Mongoose (a library) is able to detect weather you've called await or not, HERE
Here is what they're saying:
Mixing promises and callbacks can lead to duplicate entries in arrays.
For example, the below code inserts 2 entries into the tags array,
*not just 1.
const BlogPost = mongoose.model('BlogPost', new Schema({
title: String,
tags: [String]
}));
// Because there's both `await` **and** a callback, this `updateOne()` executes twice
// and thus pushes the same string into `tags` twice.
const update = { $push: { tags: ['javascript'] } };
await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => {
console.log(res);
});
How Mongoose for example was able to detect that I'm using await or not?
[EDIT] ******* After sometime, I've noticed that the answers bellow doesn't actually answer my question.
Please before answering my question: please read their comment above, they're saying: " // Because there's both await and a callback, this updateOne() executes twice"
this means: if you didn't use await, and you passed in a callback, this code will be invoked once, BUT if you used await + callback, this code will be invoked twice, therefore: the question is: how they're able to know if I've used await or not?!
Again: This means, if you didn't use await, this is going to invoke once, and if you used await, this is going to get invoked twice.
You've misunderstood what the documentation is saying.
It says that calling then will execute the query (but that passing a callback will also execute the query, so you shouldn't do both at the same time).
await is essentially alternative syntax for calling then (although it seems to do some magic with caching).
They don't detect await, they just have a method named then.
const notAPromiseButThenable = {
then: function () {
console.log("Then called");
return this;
}
};
(async function () {
await notAPromiseButThenable.then(() => null).then(() => null);
})();
How can I detect that await was mentioned before calling my function?
You can't. In fact, the function is called before await "kicks in". These two are the same:
await myFunction();
let result = myFunction();
await result;
await is not a different way to call a function, it simply operates on Promises, which of course are often returned by functions. I.e. it operates on the return value of a function.
How Mongoose for example was able to detect that I'm using await or not?
Are they actually able to detect that? Nothing in the documentation you linked seems to indicate that.
What they (and you) could do is checking whether a callback is passed or not. If one is passed then they shouldn't return a Promise and vice versa. But I guess that's not how they want to design the API.
This means, if you didn't use await, this is going to invoke once, and if you used await, this is going to get invoked twice.
Yes, but not because they detect anything but because await does something with the return value of the function. That's just how await works. So obviously, if you don't use await then the thing that await would do won't happen ;)
Here is an example:
function someFunction(value, callback) {
// Nothing in here does any "await" detection
let array = [];
if (callback) {
array.push(value);
callback(array);
}
return {
then(resolver) {
array.push(value);
resolver(array);
}
}
}
(async function() {
console.log('with await', await someFunction(42, () => {}));
someFunction(42, array => console.log('without await', array));
}());
I am trying to set a value globally but not able to solve. Here is my code
declared variable - allViewounters: any[] = [];
viewgraph() {
let formData: FormData = new FormData();
this.profileService.lineChartData(formData).subscribe((response) => {
this.allViewounters = response.allViewounters;
console.log(this.allViewounters);
});
}
I want to print the value outside the function console.log(this.allViewounters);
How can I get the value? I tried to get the value through "storageService" but in 1st load, it is not any giving value, for 2nd load it works fine, but I need in 1st page load.
So the condition that you have right now is that you have a callback function that needs to get passed as variable on subscribe(). Since you want to pass the callback result somewhere else (outside viewgraph()), you can set an outside variable to the result that you're getting (what you have done). However, the variable will only be set if the callback has been called (the condition that you have right now).
However, you want to access the value once the callback is done. There is a way in JS/TS to "wait" for a result, by wrapping it in a promise and await for the result. So let's say the library has this method:
function subscribe (callbackFn: (response: any) => void) {
// Assuming response gets passed from somewhere else
callbackFn(response);
}
However, you want to wait for this callback to be called. You can wrap the way you handle the result by using Promise in an asynchronous function:
async function viewgraph() {
let formData: FormData = new FormData();
return new Promise((resolve, reject) => {
this.profileService
.lineChartData(formData)
.subscribe((response) => {
resolve(response);
}
});
}
and then on whatever function that you want to obtain the result, call this function with await, like:
const response = await viewgraph();
Keep in mind that whatever function that calls an await needs to be asynchronous too (marked as async, just like viewgraph()).
If you want to learn more about promise handling and async/await, read this article.
I can't get flatListData because it seems to be local only
I can get flatList data just inside query.find().then(function(results)
And Outside that I got None!
I Try this with Async/Await either but doesn't work
const W = Parse.Object.extend("W");
const query = new Parse.Query(W);
var flatListData = [];
query.find().then(function(results) {
for (var i=0; i < results.length; i++){
flatListData.push(String(results[i].get("AlphabetCode")));
}
alert(flatListData) //{"a" , "b" , "s" , "w"}
});
alert(flatListData) // Nothing!
module.exports = flatListData;
The problem here is that you are trying to make an async export statement, which is strictly forbidden.
First of all, yes, flatListData is global, not locally scoped. The actual issue you are facing is that, while your query result is effectively given to your variable, it takes some time as an async function to complete. When you call your variable in your second alert(), and in your module.exports, your async query hasn’t finished yet, so new value isn’t assigned and you end up sending nothing but undefined value to your outer script.
Now the only possible way to deal with it is to force your module.exports to wait for the variable to be assigned, which means either scoping it inside your promise (along with your first alert()), or using an await statement. BUT :
MDN Documentation
The await operator is used to wait for a Promise. It can only be used inside an async function.
So there it is. Your only exit path is to scope your module.exports… Which is totally forbidden. You never want to call your exports apart from top level, aka global scope.
Redefine the problem
Your goal is to export a content set in your object, to be used from many places.
But keep in mind that you cannot export anything asynchronously. In your case, your only choice would be to export a function, and call it whenever you need your value.
Now the solution
getFlatListData.js, or whatever you called it
// Those are globally scoped. They will only be calculated on
// initialization, and not during outside import calls
const W = Parse.Object.extend("W");
const query = new Parse.Query(W);
// We wrap everything inside an async function, which will be exported
function getFlatListData() {
// We return a promise, to allow outer calls to the result
return new Promise(resolve => {
// Also, avoid var. If you need a variable which isn’t constant, prefer let.
let flatListData = [];
// Prefer arrow functions here
query.find().then(results => {
// Works like your classic for(…) loop, but more efficient
for(const result of results) {
flatListData.push(String(result.get("AlphabetCode")));
}
// Resolve your function and send the result back
resolve(flatListData);
});
});
}
module.exports = getFlatListData;
And now, in your outside script :
main.js, or whatever
// Assuming you are using commonJS syntax
const getFlatListData = require(‘path_to_your_script/getFlatListData’);
[. . .]
getFlatListData().then(result => {
// Now you can use your FlatListData aliased as result
});
// OR
const myAsyncFunction = async() => {
const myVariable = await getFlatListData();
// Use myVariable as you please now
};
Lot of improvements can be done here, such as using a map() function to assign your flatListData, or adding a reject to your promise to handle any errors. But you got the main idea.
Never make asynchronous exports, and if you have to do so, it means you need to rethink about your code !