Is it possible to pass a function to Puppeteer's page.evaluate() - javascript

I am using Puppeteer to parse a webpage and can't find a definitive answer to my question.
I am trying to pass a function as an argument to page.evaluate() An object is OK to pass but can't seem to pass a function. Here's a contrived example:
const obj = {
thing: 'thing1',
};
const myfunction = () => {
return `great ${stuff}`;
};
await page.evaluate((obj, function)=>{
const thing = myfunction;
},(obj, function));
Is it possible to pass a function as an argument to puppeteers page.evaluate()?

No, you cannot pass functions like that. The passed data needs to be serializable via JSON.stringify, which is not possible for functions.
Alternative: Expose the function
To use a function from your Node.js environment inside the page, you need to expose it via page.exposeFunction. When called the function is executed inside your Node.js environment
await page.exposeFunction('myfunction', text => `great ${text}`);
await page.evaluate(async (object) => {
return await window.myfunction(object); // 'great example'
}, 'example');
Alternative: Define the function inside page.evaluate
To use a function inside the page context, you can either define it inside of the context. This way, the function does not have access to your Node.js variables.
await page.evaluate((obj) => {
const myfunction = (stuff) => `great ${stuff}`;
return myfunction(obj); // 'great example'
}, 'example');

Related

Cloud Functions - Use global variables

I have a function cloudFunctionCall in my cloud-function setup that is invoked on every cloud function call.
import * as functions from "firebase-functions";
const mysql = require("mysql");
function cloudFunctionCall(asyncFunc) {
return functions
.region(region)
.https.onCall(async (data: any, context: any) => {
try {
const pool = mysql.createPool(getConfig(context));
const result = await asyncFunc(data, pool);
pool.end();
res.send(result);
} catch (err) {
log("A promise failed to resolve", err);
res.status(500).send(err);
}
});
}
export const func1 = cloudFunctionCall(_func1);
export const func2 = cloudFunctionCall(_func2);
context and therefore pool defines to which database an invoked function needs to talk to which might change on every cloud function call. Now I want to avoid passing pool as a parameter to asyncFunc since it is not actually required for the function logic and makes the functions more verbose.
Now my question: Is it safe to define pool as a global variable that gets updated on every cloud function call like the following?
//...
let pool;
function cloudFunctionCall(asyncFunc) {
return functions
.region(region)
.https.onCall(async (data: any, context: any) => {
try {
pool = mysql.createPool(getConfig(context));
const result = await asyncFunc(data, pool);
//...
I understand that every exported function is hosted in it's own environment but what would happen if func1 would get executed twice in quick succession on different databases? Could it happen that the second invocation overwrites pool that was set by the first invocation so that the latter one executes on the wrong database?
If so, is there another way to avoid passing pool as a parameter?
Cloud Function invocations run in strict isolation of each other: each container will only ever have one execution at a time. So there is no chance of function invocations overwriting the value of your global for each other.

Is there a way to pass a global variable into an $$eval function?

I am using page.$$eval to assess div#Menu in the browser context. I however need to pass the value in a const variable, myVar, into the browser context to perform comparisons within that context. However, I am running into a scoping issue when using $$eval. Is there a way around this?
Here is the code I made:
const myVar = 100;
const menuData = await page.$$eval("div#Menu", (Menu1) => {
return Menu1.map((Menu1Element) => {
console.log(myVar); //testing to see if myVar is passed into browser context
...
})[0];
}).catch(console.error);
The error message I get =>
Error: Evaluation failed: ReferenceError: myVar is not defined
This is the signature:
page.$$eval(selector, pageFunction[, ...args])
So you can pass args as pageFunction args (3rd parameter onwards in $$eval and these will be passed as args to your function.
This is the updated code snippet.
const myVar = 100;
const menuData = await page.$$eval("div#Menu", (Menu1, varInsideFunction) => {
return Menu1.map((Menu1Element) => {
console.log(varInsideFunction); //testing to see if myVar is passed into browser context
...
})[0];
}, myVar).catch(console.error);

How to await data from async/await debounced axios call

I'm trying to return a debounced search result from an API request using lodash's debounce function but keep getting undefined from the call.
Here's my code, please help;
const searchSuggestionsRequest = async (input) => {
const params = {
userInput: encodeURIComponent(input),
};
const { data } = await axios.get(`${BASE_URL}/api/location`, { params });
return data;
};
const debouncedSuggestionsRequest = _.debounce(searchSuggestionsRequest, 500);
const fetchSearchSuggestions = (input) => {
return debouncedSuggestionsRequest(input);
};
handleSearchSuggestions = async (input) => {
const searchObj = await fetchSearchSuggestions(input);
console.log('searchObj', searchObj);
};
handleSearchSuggestions()
You are expecting the debounce function to return the result of your original function, or in your case the resolved promise. But that is not how the debounce function works.
The debounce function wraps your function with its own code in which it checks if any new call files in our not. After a certain amount of time eventually your function is initiated. But it cannot return the result of that function.
You need to define a more global scope (or at least a scope overlapping your functions) variable, and set that variable in your function where you get the axios result.
You problem remains that you cannot await for the result, so your console.log will still be undefined. Personally I develop in Vue, and I can set a reactivity watcher on the variable.

How to pass an arg to the function inside lodash's once function?

So I am calling a function that calls lodash's once function:
if (isPageTwo) {
sendSegmentData(sendEnhancedTrackEvent);
}
And I have the functions defined here:
const pageTwoSegmentEvent = (sendEnhancedTrackEvent) => {
const enhanceableData = {
name: 'Page loaded',
properties: {
...defaultProps,
cid: getCid(),
epid: getEpid(),
name: 'ReviewExperienceModernDoubleStep'
}
};
sendEnhancedTrackEvent(enhanceableData);
}
const sendSegmentData = (sendEnhancedTrackEvent) => {
once(() => {
pageTwoSegmentEvent(sendEnhancedTrackEvent);
});
}
I am trying to pass the sendEnhancedTrackEvent callback function to the pageTwoSegmentEvent function but I guess the way I'm trying to pass it through the once function pageTwoSegmentEvent never gets called. Does anyone know how to do this?
The _.once() method takes a function (func), and returns a function that invokes the wrapped function (func) a single time. According to the docs:
The func is invoked with the this binding and arguments of the created
function.
Which means that whatever arguments you pass to the new function, will be passed to the wrapped func.
In your case:
sendSegmentData has the sendEnhancedTrackEvent param
When sendSegmentData is invoked, it calls once(() => { pageTwoSegmentEvent(sendEnhancedTrackEvent); });, which creates a new function. The new function is not returned or called.
To create sendSegmentData, call once on pageTwoSegmentEvent directly. This will return a new function, that will pass whatever arguments in gets to pageTwoSegmentEvent.
Example:
const { once } = _
const pageTwoSegmentEvent = (sendEnhancedTrackEvent) => console.log(sendEnhancedTrackEvent)
const sendSegmentData = once(pageTwoSegmentEvent)
sendSegmentData('1')
sendSegmentData('2')
sendSegmentData('3')
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
_.once returns the function that you need to invoke. No matter how many times you call this function it will only be invoked once.
Assuming once is an alias to _.once, try changing it to this:
const sendSegmentData = once((sendEnhancedTrackEvent) => {
pageTwoSegmentEvent(sendEnhancedTrackEvent);
});
...
// somewhere else call it
sendSegmentData(theSegmentedData);

js - pass extra param to callback function

I'm trying to pass an extra param to a 3rd callback function (its not my own callback).
const selectedItems = dt.rows({ selected: true });
const selectedIndexes = selectedItems.indexes();
const selectedData = selectedItems.data();
let data_object = [];
$.each(selectedData, (i, o) => {
data_object.push(o['PackageName']);
});
window.bridge.deleteApps(data_object, (success_list, selectedIndexes) => {
console.log("test"); // selectedIndexes -> undefined
});
Background: It's a function that comes with Qt that triggers a python method (pyqt), the first param is passed to python the second param is a callback function with the return from the python method (success_list) but i need selectedIndexes too.
When I do
window.bridge.deleteApps(data_object, (success_list, abc=selectedIndexes) => {
console.log("test"); // abc + selectedIndexes is available
});
I'm sorry that I've no working snippet for you to test but I did some researchs about callbacks and actually don't understand it, so I'm not able to reproduce this case.
I think part of your confusion is how callbacks are handled. Basically it's up to the window.bridge.deleteApps function to pass parameters to the callback you provide. So unless you're the author of that function, there's not a good way to have it pass you additional parameters. However, in the above example you should have access to selectedIndexes because you've declared it with const and it will be accessible from your callback.
So you should be able to have this code:
window.bridge.deleteApps(data_object, (success_list) => {
console.log(selectedIndexes); // Should be available because you've declared it in a higher scope
console.log(success_list); // Gets passed by the .deleteApss function
});

Categories