I am playing around with some office JavaScript and attempting to create an executable function from a string that is received from an API call.
The office JavaScript task pane for Excel makes a call to an external API on button click, and returns a function in the form of a String object. To make this into a function object, I have used:
var executable = new Function(response)
executable();
Unfortunately, nothing is happening, it doesn't seem to be calling the function at all.
After some debugging, I believe the reason it isn't getting called is because the response string object is already a full function, and new Function() is wrapping the response in another layer of function.
Response is:
async function highlightCells() {
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getItem("Sheet1");
const range = sheet.getRange();
range.format.fill.color = "yellow";
await context.sync();
console.log("Called");
});
}
And executable is resolving to:
function anonymous() {
async function highlightCells() {
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getItem("Sheet1");
const range = sheet.getRange();
range.format.fill.color = "yellow";
await context.sync();
console.log("Called");
});
}
}
Any ideas how to prevent the additional function wrapper from appearing? As you can see the response object is already a full function.
Do I need to use some other method of converting the string to a function or is there a way to override the wrapper within the new Function() syntax?
If you don't know the function name in advance, you can wrap the function definition in brackets to call it.
let response = `async function test() {
console.log("function called");
}`;
let executable = new Function(`(${response})();`);
executable();
If you need to pass it arguments or await it, make it return the function and call the function to get your actual function.
let func = `async function sum(a,b) {
return new Promise(resolve => setTimeout(() => resolve(a+b), 1000));
}`;
let executable = new Function(`return ${func};`)();
(async () => {
let val = await executable(3,4);
console.log("the sum is", val);
})();
If you know that it is guaranteed to be a function you could directly invoke it in the Function:
let data = 'function(arg1, arg2) { return arg1 + " " + arg2 }'
let func = new Function(`return (${data}).apply(this, arguments)`)
console.log(func(1,2))
With .apply(this, arguments) you call that function and pass the arguments you pass to your Function object into the received function. And the return returns the result that function.
Passing this to apply ensures that the Function object could be stored in an object and that the function your received could access that object using this. This might not be required, but makes the function behave like a regular function:
let data = 'function(arg1, arg2) { return arg1 + " " + arg2 + " " + this.prop }'
let obj = {prop : 'somevalue'}
obj.func = new Function(`return (${data}).apply(this, arguments)`)
console.log(obj.func(1, 2))
Simply use eval instead of new Function. You need to force the code to be an expression, not a statement, that's what the 0, part is for.
code = `function test() {
console.log('hey!')
}`
let executable = eval('0,' + code)
executable()
var executable = new Function(response + ';highlightCells()')
executable();
Since the returned code is a complete executable statement, use eval() to execute it, not new Function().
eval(response);
highlightCells();
Note that this requires that you know the name of the function that's being defined in the response. If not, you need to write code that parses it to extract the function name.
Related
This is one of those job interview tests on HackerRank that I am embarrassed to say I didn't complete within the allotted 20 minutes, and it's driving me nuts. Basically, you're given the following:
function myList() {
// write code here
}
function main() {
// You can't edit this function. This is just pseudo, from what I can remember
const obj = myList();
if (operation === "add")
obj.add(item);
else if (operation === "remove")
obj.remove(item);
else
obj.getList();
// Some more stuff here
}
So my first train of thought was to write the following inside myList():
let objList = [];
function add(item) {
objList.add(item)
}
// etc., etc.
return {add, remove, getList};
Needless to say, the above didn't work, with the error: obj.add is not a function, in the main function.
I tried experimenting with this and the returns (only returning add, with or without {}), but didn't really get anywhere. What could I have done to get this working?
As the answer from Attersson already suggested, you could use a closure inside the myList function. This would allow to only expose the three methods add, remove and getList while the original list cannot be accessed. Here is an example:
function myList() {
return (() => {
const obj = {itemList: []}
const add = (item) => obj.itemList.push(item)
const remove = (item) => obj.itemList = obj.itemList.filter(i => i !== item)
const getList = () => obj.itemList
return {add, remove, getList}
})()
}
For obj, inside myList() you could have run a closure, that is a self invoking function returning, in this case, an object with your k:v exports, but with private access to the context of the now expired anonymous caller. With "const add = function" etc statements you can define values for your exports, while the objList goes in the private context.
I tried searching for this, but was unable to find something that matched the case I needed.
I have this function here that can't be modified:
function generate() {
const delay = 7000 + Math.random() * 7000;
const num = Math.random();
return (callback) => {
setTimeout(() => {
callback(num);
}, delay);
};
}
When I try to call the function like generate() I get an error. I've also tried using a promise based approach like:
const result = await generate();
return result;
But when I do that the result that is returned is a promise, which I can't render into JSX (I'm using React).
JSX component code (For debugging purposes currently):
const Test = () => {
return <>{generate()}</>;
};
I would appreciate any suggestions here. Thanks!
This in no way is to solve your problem, but only to understand this logic. Maybe it can help in solving the problem.
generate() in this case returns a function that sets the timeout.
typeof generate() = "function"
// this is then the function we use to run the long running function
const generateFunction = generate();
//we then create the callback handler function
const callback = () => {...}
//when executed it will set the timeout to the random delay and display the result in your component of oyur choice.
generateFunction(callback);
fiddle
I have an exercise where I have to give three arguments: function and two numbers.
The function which I give has to be activated (each) after x miliseconds for y miliseconds.
I wanted to make helper like this:
function helper(string) {
console.log("Printing string which was given: " + string)
}
but when I do it like this and I try to enable my function ex1_4(helper("some string"), 500,5000) I get an error that Callback must be a function
What am I doing wrong?
function ex1_4(func, x,y) {
const resFunction = setInterval((func), x)
const stop = setTimeout(() => {clearInterval(resFunction)}, y)
}
function helper(string) {
console.log("Printing string which was given: " + string)
}
ex1_4(helper("some string"),500,5000)
helper("some string")
Is a function call which returns a value, in your case it is undefined. If you want to make it into a callback, you need to wrap it in a function like so:
() => helper(“some string”)
In your code:
ex1_4(() => helper("some string"),500,5000)
When you add helper("some string"), you're actually executing the method, instead of sending the method to ex1_4. You should type something like ...
ex1_4(helper,500,5000) // no parenthesis
... just like you did with setInterval((func), x).
However, you want to add parameters to helper, and in this case, you can use bind. You should really learn about bind(), call(), and apply().
ex1_4(helper.bind(null, "some string"),500,5000)
function ex1_4(func, x,y) {
const resFunction = setInterval((func), x)
const stop = setTimeout(() => {clearInterval(resFunction)}, y)
}
function helper(string) {
console.log("Printing string which was given: " + string)
}
ex1_4(helper.bind(null, "some string"),500,5000)
As I saw in document (here), we can expose js function to page like this,
function do_many_operations(elem) {
let rect = elem.getBoundingClientRect();
return rect;
}
async function dummy_fn1(page) {
// Expose function here
await page.exposeFunction("do_many_operations", do_many_operations);
let temp = await page.evaluate(async (a, b) => {
let elems = document.querySelectorAll("p");
// Use function here
let rect = await do_many_operations(elems[0]);
}, 1, 2);
}
However, the code breaks with error message saying,
Error: Evaluation failed: TypeError: elem.getBoundingClientRect is not a function
That means the elem arg to do_many_operations is not a valid element or node, WHY??
Another question is, can we inject functions to browser context? So that we don't have to inject the function again and again for new pages created.
There is a serialization process when you call a function registered with exposeFunction.
When you call do_many_operations(elems[0]);, elems[0] is serialized (JSON.stringify) and passed to your function in Node. On the node side, that object won't have a getBoundingClientRect function.
If you want to reuse code you can create functions on the browser side using evaluate:
await page.evaluate(() => {
window.do_many_operations = function(elem) {
let rect = elem.getBoundingClientRect();
return rect;
}
});
I have a file called paintTiming.js, which uses the Paint Timing API for finding out Web Performance paramters such as First Paint and First Contextual Paint. The file contents are shown below:
var FP, FCP, obj = [];
function parent() {
if ("PerformanceObserver" in window) {
let observerPromise = new Promise((resolve, reject) => {
// access the PerformanceObserver interface
let observer = new PerformanceObserver((list) => {
resolve(list);
});
observer.observe({
entryTypes: ["paint"]
});
}).then((list) => {
// Find out First Paint and First Contextual Paint
FP = list.getEntries()[0].startTime;
FCP = list.getEntries()[1].startTime;
// Store in array
obj[0] = FP;
obj[1] = FCP;
element = "The paint times are: <br> First Paint : " + FP + "ms, <br> First Contentful Paint : " + FCP + "ms";
// show values on web page
document.getElementsByTagName('p')[1].innerHTML = element;
}).then(() => {
// check if array is created, and is functioning
console.log(obj[0]);
}).then(() => {
// create function
function abc() {
return {
a: obj[0],
b: obj[1]
};
};
}).catch((error) => {
console.warn(error);
});
}
};
As you can see, a function abc() should be created when the promise is resolved.
My question : How do I use this function outside the promise?
For example, consider the sample below (used in a HTML file):
<script type="text/javascript" src="paintTiming.js"></script>
<script type="text/javascript">
// should return object containing FP and FCP values
console.log(abc());
</script>
The console shows that the function is not defined. If the JS code is inline, it still doesn't work.
You may not able to use the function if this is declared inside .then. Because this function abc is private to the callback function passed to .then & .then will only execute with the ajax.
Alternatively you can define this function outside the Promise and call this function inside the .then callback. Also need to bind the context using this