How to execute possibly async JS code passed in by string

January 21, 2023

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.


Profile picture

Written by Dušan Roštár - the "mr edge case" guy
my twitter : rostacik, my linkedin : rostar
Reach me at dusan at thisdomain.net