TypeScript call function in dynamic (anonymous) function

26.9k views Asked by At

I am trying to create a dynamic function in TypeScript which calls an already existing function like:

let dynamicFunction = new Function("existingFunction(\"asdf\");");

function existingFunction(name: string) {
    console.log(name);
}

While debugging in chrome dynamicFunction looks like this:

(function() {
existingFunction("asdf");
})

When I try to execute dynamicFunction, it says "Uncaught ReferenceError: existingFunction is not defined", which is no surprise because it's a different scope, but how can I actually call exisitingFunction inside dynamicFunction?

Any help would be greatly appreciated!

Edit:

to be more precise: I've got a typescript file which contains one module. This module exports a function which should return the created dynamic function. The created dynamicFunction is then used in another module which actually contains the exisitingFunction.

I've chosen this approach because I need to convert a given string to an executable condition, which will be executed many times.

For example: convert string "VALUE==1" to:

function () {
    return exisitingFunction("VALUE") == 1;
}

A short example of how it should look like:

parser.ts:

export module Parser {
   export function getFunction(expression: string) {
      // Calculating condition...
      let condition = "existingFunction(\"VALUE\") == 1;"
      return new Function(condition);
   }
}

condition.ts:

import { Parser } from "./parser";
class Condition {
    // getting the DynamicFunction
    private _dynamicFunction = Parser.getFunction("VALUE==1");

    someFunctionInsideCondition() {
       // Calling the DynamicFunction
       this._dynamicFunction();
    }
}

// Maybe this function should be somewhere else?
function existingFunction(name: string) {
    console.log(name);

    return 1;
}

I hope this explains my problem a little bit better.

2

There are 2 answers

4
alebianco On BEST ANSWER

From the Function documentation

Functions created with the Function constructor do not create closures to their creation contexts; they always are created in the global scope. When running them, they will only be able to access their own local variables and global ones, not the ones from the scope in which the Function constructor was called. This is different from using eval with code for a function expression.

so you'll have to pass existingFunction as an argument or define it in the global space.

try with

var existingFunction = function(name: string) {
    console.log(name);
}

Also have a look at eval which will give you access to the current scope ...

--- Update

After the question update and considering your comment about not wanting to use eval because of security concerns (with which i totally agree)

The problem is that in the generated function's scope, this is undefined. Making your existingFunction part of the global scope is already a bad idea and between Typescript and the modules architecture doesn't seem possible at all.

So why not passing a context to the generated function?

This will allow you to control how much of your application to expose to the generated function, while giving it access to external methods.

Something along the lines of:

class Parser {
    static getFunction(expression) {
        let condition = new Function("context", "context.existingFunction(\"VALUE\") == 1;");
        return condition;
    }
}

class Condition {
    constructor() {
        this._dynamicFunction = Parser.getFunction("VALUE==1");
    }

    someFunctionInsideCondition() {
        // Calling the DynamicFunction
        this._dynamicFunction(this);
    }

    existingFunction(name) {
        console.log("hello " + name);

        return 1;
    };
}



let c = new Condition();
c.someFunctionInsideCondition();

Of course your context can be a different object instead of this, where you keep all your utility functions.

I had to donwpile (compile it down, my own word) to es2015 to make the example run here, but I made it originally in Typescript and works fine

1
Louis On

I would skip the usage of new Function and instead do it as follows.

The parser.ts file would contain this:

export class FunctionGenerator {
    constructor(private fn: Function) {}

    makeFunction(args: string): Function {
        const [variable, val] = args.split("==");
        return () => this.fn(variable) == val;
    }
}

This is basically a factory that allows creating a series of functions that call the function passed when the factory is created. You can then use makeFunction for the specific checks you want to perform. (Note that I used == like in your question. I much prefer using === unless there's a reason against it.)

It can then be used like this:

import * as parser from "./parser";

let vars = {};

// This is a simulation of your funciton. It just plucks values from `vars`.
function existingFunction(name: string) {
    return vars[name];
}

function resetVars() {
   vars = {
    "VALUE": 1,
    "FOO": 2,
    "BAR": 3,
   };
}

function test(gen) {
    const fn1 = gen.makeFunction("VALUE==1");
    console.log(fn1(), "should be true");

    const fn2 = gen.makeFunction("BAR==3");
    console.log(fn2(), "should be true");

    vars["BAR"] = 7;
    // Call the same function again, but with a new value in `vars`.
    console.log(fn2(), "should be false");

    const fn3 = gen.makeFunction("BAR==1000");
    console.log(fn3(), "should be false");
}

resetVars();
const gen = new parser.FunctionGenerator(existingFunction);
test(gen);