40

Is there any way to create a function with a real name that's determined at runtime without using eval, and using only pure JavaScript? (So, no generated script elements, as those are specific to the browser environment [and in many ways would be eval in disguise anyway]; no using non-standard features of one particular JavaScript engine, etc.)

Note that I'm specifically not asking about anonymous functions referenced by variables or properties that have names, e.g.:

// NOT this
var name = /* ...come up with the name... */;
var obj = {};
obj[name] = function() { /* ... */ };

There, while the object property has a name, the function does not. Anonymous functions are fine for lots of things, but not what I'm looking for here. I want the function to have a name (e.g., to show up in call stacks in debuggers, etc.).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875

3 Answers3

52

The Answer for ECMAScript 2015+ (aka "ES6"):

Yes. As of ES2015, the function created by an anonymous function expression assigned to an object property takes the name of that object property. This is implemented in all modern browsers, although Edge and Safari don't use the name in stack traces. We can use that in combination with another ES2015 feature (computed property names) to name a function without new Function or eval.

In ES2015 this creates a function named "foo###" where ### is 1-3 digits:

const dynamicName = "foo" + Math.floor(Math.random() * 1000);
const obj = {
  [dynamicName]() {
    throw new Error();
  }
};
const f = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + f.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
  f();
} catch (e) {
  console.log(e.stack);
}

It would also work with [dynamicName]: function() { }, method syntax isn't required, function syntax is fine. Which is handy if you want to create a constructor function this way:

const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
    [dynamicName]: function(throwError = false) {
        if (throwError) {
            throw new Error();
        }
    }
};
const F = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + F.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
  new F(true);
} catch (e) {
  console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true

Of course, this is ES2015+, so you could also use class to create a constructor, [dynamicName]: class { }:

const dynamicName = "Foo" + Math.floor(Math.random() * 1000);
const obj = {
    [dynamicName]: class {
        constructor(throwError = false) {
            if (throwError) {
                throw new Error();
            }
        }
    }
};
const F = obj[dynamicName];
// See its `name` property
console.log("Function's `name` property: " + F.name + " (see compatibility note)");
// We can see whether it has a name in stack traces via an exception
try {
  new F(true);
} catch (e) {
  console.log(e.stack);
}
// And we can see it works as a constructor:
const inst = new F();
console.log(inst instanceof F); // true

The Answer for ECMAScript 5 (from 2012):

No. You cannot do that without eval or its cousin the Function constructor. Your choices are:

  1. Live with an anonymous function instead. Modern engines do things to help debugging with those.

  2. Use eval.

  3. Use the Function constructor.

Details:

  1. Live with an anonymous function instead. Many modern engines will show a useful name (e.g., in call stacks and such) if you have a nice, unambiguous var name = function() { ... }; expression (showing the name of the variable), even though technically the function doesn't have a name. In ES6, functions created that way will actually have names if they can be inferred from the context. Either way, though, if you want a truly runtime-defined name (a name coming from a variable), you're pretty much stuck.

  2. Use eval. eval is evil when you can avoid it, but with strings you're in total control of, in a scope you control, with an understanding of the costs (you're firing up a JavaScript parser), to do something you cannot do otherwise (as in this case), it's fine provided you really need to do that thing. But if you're not in control of the string or scope, or you don't want the cost, you'll have to live with an anonymous function.

    Here's how the eval option looks:

    var name = /* ...come up with the name... */;
    var f = eval(
        "(function() {\n" +
        "   function " + name + "() {\n" +
        "       console.log('Hi');\n" +
        "   }\n" +
        "   return " + name + ";\n" +
        "})();"
    );
    

    Live example | Live source

    That creates a function with the name we come up with at runtime without leaking the name into the containing scope (and without triggering the flawed handling of named function expressions in IE8 and earlier), assigning a reference to that function to f. (And it formats the code nicely so single-stepping through it in a debugger is easy.)

    This didn't used to correctly assign the name (surprisingly) in older versions of Firefox. As of the current version of their JavaScript engine in Firefox 29, it does.

    Because that uses eval, the function you create has access to the scope in which it was created, which is important if you're a tidy coder who avoids global symbols. So this works, for instance:

    (function() {
        function display(msg) {
            var p = document.createElement('p');
            p.innerHTML = String(msg);
            document.body.appendChild(p);
        }
    
        var name = /* ...come up with the name... */;
        var f = eval(
            "(function() {\n" +
            "   function " + name + "() {\n" +
            "       display('Hi');\n" +         // <=== Change here to use the
            "   }\n" +                          //      function above
            "   return " + name + ";\n" +
            "})();"
        );
    })();
    
  3. Use the Function constructor, as demonstrated in this article by Marcos Cáceres:

    var f = new Function(
        "return function " + name + "() {\n" +
        "    display('Hi!');\n" +
        "    debugger;\n" +
        "};"
    )();
    

    Live example | Live source

    There we create a temporary anonymous function (the one created via the Function constructor) and call it; that temporary anonymous function creates a named function using a named function expression. That will trigger the flawed handle of named function expressions in IE8 and earlier, but it doesn't matter, because the side-effects of that are limited to the temporary function.

    This is shorter than the eval version, but has an issue: Functions created via the Function constructor do not have access to the scope in which they were created. So the example above using display would fail, because display wouldn't be in-scope for the created function. (Here's an example of it failing. Source). So not an option for tidy coders avoiding global symbols, but useful for those times when you want to disassociate the generated function from the scope in which you're generating it.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • What is so bad about an anonymous function? – PiTheNumber Feb 28 '12 at 09:16
  • @PiTheNumber: I didn't say they were bad. :-) In fact I said *"Anonymous functions are fine for lots of things..."* But anonymous functions show up in call stacks and such as "(anonymous)" (or similar), which is less than useful. I like to [help my tools help me](http://blog.niftysnippets.org/2010/03/anonymouses-anonymous.html), so I give my functions names whenever I can. In this case, I'm writing a function factory function in a library, and deciding whether it's worth having it (optionally) assign the functions real names, or living with no name or a static name in this case. – T.J. Crowder Feb 28 '12 at 09:21
  • 2
    mh, I see you point. Well, I will think about avoiding anonymous functions. Thanks! – PiTheNumber Feb 28 '12 at 09:30
  • 2b: use `new Function()` to build the function, rather than eval'ing the entire string? at least that way you can catch syntax as well as runtime errors in try/catch/finally blocks. – Mike 'Pomax' Kamermans Jul 29 '13 at 21:09
  • 1
    @Mike'Pomax'Kamermans: Two problems with that. :-) 1. You can't give the function a real name with `new Function`, and 2. You *can* catch syntax errors by wrapping the `eval` in a `try/catch` block, just as you can with `new Function`. There's no difference in that regard. – T.J. Crowder Jul 29 '13 at 21:11
  • actually, you can't. An `eval("monkey = 4; enfeknfeo")` isn't atomic. It'll fail on the "enfeknfeo", after happily running the global monkey var declaration. After you catch the syntax error, that variable still exists, as opposed to `new Function`, which fails atomically. – Mike 'Pomax' Kamermans Jul 29 '13 at 21:28
  • @Mike'Pomax'Kamermans: There is no syntax error there. There's a `ReferenceError`, and `new Function` doesn't help. `var f = new Function("monkey = 4; enfeknfeo");` causes no error, and then `f()` throws the `ReferenceError` after creating the implicit global `monkey`, just like `eval`'ing the code does. `eval("frog = 42; }")` throws a `SyntaxError`, *without* creating the implicit `frog` global (as does `new Function("frog = 42; }")`). `new Function` does let you parse the code without running it, but it's not more "atomic" AFAICS. And it doesn't help give my function a name. :-) – T.J. Crowder Jul 30 '13 at 02:52
  • 1
    @DhruvPathak: It was a very, very useful article in its day. It's several years out of date. The only significant NFE issues remaining are in IE8; all other engines (including IE9+) get them right. More [*Double-take*](http://blog.niftysnippets.org/2010/09/double-take.html) – T.J. Crowder Jun 04 '14 at 07:56
  • 1
    Oh nice usage of computed properties :-) I had loved to see this in a separate answer so that I could upvote it separately… – Bergi May 19 '15 at 03:35
  • 1
    for `eval` and `new Function`. I suggest `function replaceHere(){...}.toString()` instead of a multiline string. I've also seen it used in a userscript that was injecting itself into the page by creating a new script element. – John Dvorak Jul 26 '15 at 13:03
  • @JanDvorak: Good idea. Fortunately, ES6 makes a lot of this irrelevant, though. – T.J. Crowder Jul 26 '15 at 13:53
  • Whats the name of this ES6 feature when looking it up on https://kangax.github.io/compat-table/es6/ or somewhere else? Which chrome version is the first to support this? I think that this doesnt work on chrome 50 – ditoslav May 05 '16 at 09:29
  • @DominikDitoIvosevic: [function "name" property](https://kangax.github.io/compat-table/es6/#test-function_name_property). Chrome 50 fully supports it, but only behind a flag. – T.J. Crowder May 05 '16 at 09:34
  • can we able to intialize with new and eval – Kumaresan Sd Jul 30 '19 at 10:06
  • @kumaresan_sd - I'm afraid I don't understand your comment...? – T.J. Crowder Jul 30 '19 at 11:03
  • @T.J.Crowder, Sorry. Can we able to intiablize funtion without using eval() or new function() ? – Kumaresan Sd Jul 30 '19 at 11:49
  • @kumaresan_sd - Yes, the answer above shows how to do that in ES2015+. – T.J. Crowder Jul 30 '19 at 12:22
  • This is a good answer -- esp. the ES6 option -- for functions. But, unfortunately, this option makes functions only, *not* constructors, and `new ({ ['abc']() {} }).abc()` is illegal in ES6. – Codesmith Feb 18 '20 at 22:15
  • @Codesmith - See the note after the code snippet, just do `[dynamicName]: function() { }` instead of `[dynamicName]() { }` (or `[dynamicName]: class { }`, since this is ES2015+). The only reason `new` doesn't work with `[dynamicName]() { }` is that methods aren't constructors. :-) I've beefed up that bit some. – T.J. Crowder Feb 19 '20 at 08:16
9

Here's a utility function I came up with some time ago. It uses the Function constructor technique as outlined in @T.J.Crowder's great answer, but improves on its disadvantages and allows fine-grained control over the scope of the new function.

function NamedFunction(name, args, body, scope, values) {
    if (typeof args == "string")
        values = scope, scope = body, body = args, args = [];
    if (!Array.isArray(scope) || !Array.isArray(values)) {
        if (typeof scope == "object") {
            var keys = Object.keys(scope);
            values = keys.map(function(p) { return scope[p]; });
            scope = keys;
        } else {
            values = [];
            scope = [];
        }
    }
    return Function(scope, "function "+name+"("+args.join(", ")+") {\n"+body+"\n}\nreturn "+name+";").apply(null, values);
};

It allows you being tidy and avoiding complete access to your scope via eval, e.g. in the above scenario:

var f = NamedFunction("fancyname", ["hi"], "display(hi);", {display:display});
f.toString(); // "function fancyname(hi) {
              // display(hi);
              // }"
f("Hi");
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

Also

let f = function test(){};
Object.defineProperty(f, "name", { value: "New Name" });

Would accomplish the same as the accepted answer

console.log(f.name) // New Name

however neither shows "New Name" when printing the function console.log(f) // test(){}

aljgom
  • 7,879
  • 3
  • 33
  • 28
  • Under node (15) I'm seeing "[Function: New Name]" and "New Name" using `console.log`. Now using `console.log('%s', f)` does still print out the original source "function test(){}" but that's expected. Your answer is good. – Shenme Feb 04 '21 at 09:01