IF you have to do it
So first things first, the eval
in JS is just a wrong thing to do. Unless you have to.
Yes, you might have seen it in your (I hope old) code base, yes it worked for some time, but it is a bad thing to do - more on this here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval! or here https://www.digitalocean.com/community/tutorials/js-eval or here https://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea.
OK, I dont want for each eval
call a small kiten to die somewhere, what could be an option? Good news is there is something better : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function.
Sure, that might work. But what if my JS function might be async code you say? Well… there is a hack https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction (also seen here https://davidwalsh.name/async-function-class) which will give us exactly what we need.
This is how you could do it
So why not to give it a round : https://jsfiddle.net/rostacik/qc1ows6t/
I tried to make a simple function that would evaluate a valid JS function (sync or async) call and resolve
or reject Promise
that is returned so I made this thing in TS :
//shim obj until we have native obj in JS
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
/**
* will wrap function from string with promise that can be awaited
* @param handler snippet of JS to run
*/
export function awaitFromString(handler: string): Promise<unknown> {
return new Promise((resolve, reject) => {
new AsyncFunction(
"resolve",
"reject",
`try { await ${handler}; resolve(); } catch (e) { reject(e); }`,
)(resolve, reject)
})
}
in pure JS I was able to get away with this :
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
function awaitFromString(handler) {
return new Promise((resolve, reject) => {
new AsyncFunction(
"resolve",
"reject",
`try { await ${handler}; resolve(); } catch (e) { reject(e); }`,
)(resolve, reject)
})
}
The solution was not that easily to be found, in my search I even seen this link https://gist.github.com/tejacques/0745b445cf0bca3c4462 by CTO of Tinder so I suppose someone already investigated how to do such a thing but only the small “hack” with getting the AsyncFunction object did the trick since just making new Function
in JS will make a sync function in which await inside will fail.
And if you ask if it is safe to await possibly synchronous function, yet it is completely fine thing to do https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#conversion_to_promise.
What would be the outcome
There is a nice description what happens when you try to make a new Function()
in JS here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function#description but a bit better is to see things in debugger (as always)… so this is what you see when you make ordinary new Function ()
:
Hope this helps.