Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create async function

I am wondering if it is possible to dynamically create an async function like this:

new Function('await Promise.resolve()');

Expectedly, the previous code throw:

Uncaught SyntaxError: await is only valid in async function
like image 654
Franck Freiburger Avatar asked Dec 22 '17 17:12

Franck Freiburger


2 Answers

Yes, you can get a reference to the non-global AsyncFunction constructor to dynamically create async functions.

You get a reference to the AsyncFunction constructor like this:

const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

The signature of the AsyncFunction constructor is:

AsyncFunction(arg0?, arg1?, ...args?, functionBody: string);

NOTE: The AsyncFunction contructor is not global (like Function is), the only way to get a reference to it is via the prototype of an async function(){} instance, as shown above and in the MDN docs.


Example 1. Simple function without parameters:

    const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
    
    const myAsync = new AsyncFunction(`return true`);
    
    const result = await myAsync(); // true
    
    myAsync().then((result) => {
        // result is true
    });

The example above is equivalent to:

    const myAsync = async () => {
        return true;
    };

Example 2. Defining parameters:

    const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
    
    // Define two named arguments: inValue, delay
    const asyncFn = new AsyncFunction('inValue', 'delay', `return new Promise((resolve) => {
        setTimeout(() => {resolve(inValue)}, delay);
    });`);
    
    // resolves to 'hello' after 100ms
    const result = await asyncFn('hello', 100);
    
    // After 200ms the promise will be resolved with the value 'world'
    asyncFn('world', 200).then((result) => {
        console.log(result);
    });

The example above is equivalent to:

    const asyncFn = async (inValue, delay) => {
        return new Promise((resolve) => {
            setTimeout(() => {resolve(inValue)}, delay);
        });
    };

Another cool thing is that the parameters are allowed to use defaults, destructuring, and rest parameters.

    const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
    
    // Define defaults for inValue and delay
    const asyncFn = new AsyncFunction('inValue = "Hello"', 'delay = 100', `return new Promise((resolve) => {
        setTimeout(() => {resolve(inValue)}, delay);
    });`);
    
    // resolves to 'hello' after 100ms (because we defined default parameters)
    const result = await asyncFn();
    
    // resolves to 'world' after 200ms
    const result = await asyncFn('world', 200);

Example 3. Higher-level function constructor

Just note that it might be insecure to construct a function like this if the delay value were to come from an untrusted source, like user input.

    const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
    
    function createAsync(delay){
        return new AsyncFunction('inValue', `return new Promise((resolve) => {
            setTimeout(() => {resolve(inValue)}, ${delay});
        });`);
    }
    
    const asyncFn = createAsync(100);
    const asyncFn2 = createAsync(200);
    
    // After 100ms this will resolve 'hello'
    await result1 = asyncFn('hello');
    
    // After 200ms this will resolve 'world'
    await result2 = asyncFn2('world');

This is roughly equivalent to:

    function createAsync(delay){
        return async (inValue) => {
            return new Promise((resolve) => {
                setTimeout(() => {resolve(inValue)}, delay);
            };
        };
    }
    
    const asyncFn = createAsync(100);
    
    // After 100ms this will resolve 'hello'
    await result1 = asyncFn('hello');
    
    // After 100ms this will resolve 'world'
    await result2 = asyncFn('world');

And remember that you can use await inside the dynamically created async function :)

Note that the same security considerations apply to the async function constructor as the Function constructor and eval().

If you are constructing a new function with content received from an untrusted source then your script or application may be vulnerable to injection attacks.

like image 147
Sly_cardinal Avatar answered Sep 21 '22 11:09

Sly_cardinal


Don't use new Function(). I'd personally say NEVER use it unless you're a compiler because:

  1. It's ugly
  2. Code in strings is sooo 90s.. and it's ugly

Creating functions dynamically in js simply requires you to declare a function expression:

function functionMaker() {
    return function () {};
}

Therefore creating an async function dynamically is simply:

function asyncMaker() {
     return async function () {};
}
like image 31
slebetman Avatar answered Sep 21 '22 11:09

slebetman