Skip to content

Naming arrow functions #6433

Closed
Closed

Description

Hi there,

Not sure whether this has been already mentioned before (I would be surpirsed if it never was)... Wouldn't it be a good idea to name arrow functions, which is what apparently ES6 does?. Think about the benefits of this in the stacktrace. It's quite useful to have a "named" function in there, instead of an anonymous one ;-)

So, this:

const double = x => x * 2;

would be transpiled down to:

var double = function double(x) {
  return x * 2;
};

Just to mention that Babel does it like that ;-)

Thanks.

Activity

  1. adidahiya commented on Jan 11, 2016

    @adidahiya
    Contributor

    This would also be nice to have for arrow functions used as class methods/properties.

    private handleDocumentClick = (...) => {
        ...
    }
  2. RyanCavanaugh commented on Jan 11, 2016

    @RyanCavanaugh
    Member

    Wouldn't it be a good idea to name arrow functions, which is what apparently ES6 does?

    Can you clarify this part?

  3. RyanCavanaugh commented on Jan 11, 2016

    @RyanCavanaugh
    Member

    This would also be nice to have for arrow functions used as class methods/properties.

    Note that we can't safely name class methods -- a reference to handleDocumentClick in the method body must refer to whatever that name means in the enclosing lexical scope, not the function itself.

  4. RyanCavanaugh commented on Jan 11, 2016

    @RyanCavanaugh
    Member

    Consulted with Brian Terlson (@bterlson) and we think this is a spec-affecting behavior, specifically the places where the abstract SetFunctionName operation is invoked https://tc39.github.io/ecma262/#sec-setfunctionname

    Regular function expressions have very noncomplaint behavior in current runtimes and should probably just be left alone (specifically, Edge, Chrome, and Node all produce an own property of name on functions, when they shouldn't)

  5. jscriptcoder commented on Jan 12, 2016

    @jscriptcoder
    Author

    Hi Ryan Cavanaugh (@RyanCavanaugh), answering your question... as far as I know in ES6 if an arrow function is defined on the right-hand-side of an assignment expression, the ‎engine will take the name on the left-hand-side and set the arrow function's ".name" based on that

    Thanks

  6. nojvek commented on May 21, 2016

    @nojvek
    Contributor

    Hi Ryan Cavanaugh (@RyanCavanaugh), could you explain the SetFunctionName operation? I couldn't fully grasp why it should be an issue.

    I tried this in chrome.

    let l = {log(){}}
    console.log(l.log.name) //outputs 'log'
    

    Generated typescript:

    var l = { log: function () { } };
    l.log.name // empty
    

    So it seems under the covers v8 makes nice named anonymous functions.

  7. basarat commented on May 24, 2016

    @basarat
    Contributor

    Tried the following in the chrome dev console. Was surprised 🌹

    let foo = function(){}
    foo.name; // "foo"

    image

  8. dsherret commented on May 25, 2016

    @dsherret
    Contributor

    Basarat Ali Syed (@basarat) It didn't happen for me in v50.0.2661.102m, but I just upgraded to v51.0.2704.63m and now it's doing that.

  9. modified the milestones: TypeScript 2.1, on Sep 29, 2016
  10. 33 remaining items

  11. remorses commented on Mar 27, 2020

    @remorses

    Can someone implement this please?

  12. jednano commented on May 8, 2021

    @jednano
    Contributor

    This appears to be fixed now, no?

  13. RyanCavanaugh commented on May 11, 2021

    @RyanCavanaugh
    Member

    jedmao no. The relevant test would be

    const n = () => "hello";

    which should produce

    var n = function n() { return "hello"; };
                     ^               

    under target: es5

  14. rbuckton commented on Oct 5, 2021

    @rbuckton
    Contributor

    At this point I'm not sure its entirely necessary to fix this, except in possibly a very small number of cases. If you emit to ES5/ES3, but are running in an ES2015+ host, function names are automatically assigned from their variables using NamedEvaluation (which evolved from variable declaration evaluation in ES2015).

    This means that if you have the following typescript:

    const f = () => {};
    console.log(f.name);

    And we transpile it to the following ES5:

    var f = function() {};
    console.log(f.name);

    Running that transpiled output will print f. However, if you are running in an ES5/ES3 host environment, all bets are off. .name wasn't standardized until ES2015, so there are no guarantees if you'll get "f", "", or undefined. The number of ES3/ES5 runtime environments is vanishingly small, and many (if not most) libraries and frameworks are dropping support for them.

    Broadly emulating NamedEvaluation for ES5 emit would artificially inflate the overall emitted file size, for little benefit. In fact, the only place where I could see it have value is for downleveled exports and class fields, as property assignments do not call NamedEvaluation:

    // ts
    export const f = () => {};
    console.log(f.name);
    class C {
      x = () => {};
    };
    console.log(new C().x.name);
    
    // es5 (commonjs)
    exports.f = function () {};
    console.log(exports.f.name);
    
    var C = (function () {
      function C() {
        this.x = function() {};
      }
      return C;
    })();
    console.log(new C().x.name);

    In ES2015+ with native ES modules, the above TS code prints f and x, but when targeting ES5 and commonjs, it prints empty strings.

    This could possibly be resolved by leaning on NamedEvaluation for exports:

    // es5 (commonjs)
    var f = function() {}
    exports.f = f;
    console.log(exports.f.name); // prints 'f' in an ES2015+ host

    Classes are a bit trickier if there is a naming conflict (though we could rename local variables to accommodate):

    // ts
    class C {
      x = () => {};
      constructor() {
        var x = 1;
      }
    };
    
    // es5 (commonjs)
    // option 1, rename locals
    var C = (function () {
      function C() {
        var x = function() {};
        this.x = x;
        
        var x_1 = 1;  // renamed local `x` to `x_1`
      }
      return C;
    })();
    
    // option 2, leverage _NamedEvaluation_ using object literals
    var C = (function () {
      function C() {
        this.x = { x: function () {} }.x; // NamedEvaluation gives the name `x` to the function
        
        var x = 1;  // local not renamed
      }
      return C;
    })();

    One of the reasons to lean on NamedEvaluation is that we can't blindly add a name to a function expression as that can introduce scope conflicts. The name introduced in the function expression is locally scoped to the function expression body. Consider the two following cases:

    // case 1
    export let fib = (i: number) => i === 1 ? 0 : i === 2 ? 1 : fib(i - 1) + fib(i - 2);
    const fib2 = fib;
    fib = i => { console.log("i:", i); return fib2(i); };
    console.log("# case 1");
    console.log("result:", fib(3));
    
    // case 2
    function f(s: string) { console.log(s); }
    class C {
      f = (s: string) => { f("Hello " + s); }
    }
    
    console.log();
    console.log("# case 2");
    new C().f("Alice");

    If you emit to ESNext using native ES modules, you would get the following results:

    # case 1
    i: 3
    i: 2
    i: 1
    result: 1
    
    # case 2
    Hello Alice
    

    If we were to emit to ES5 and inject names into the functions, the output would change due to scope conflicts:

    # case 1
    i: 3
    result: 1
    
    # case 2
    Error: Stack overflow
    

    The reason for this would be the resulting emit:

    exports.fib = function fib(i) { return i === 1 ? 0 : i === 2 ? 1 : fib(i - 1) + fib(i - 2) };
    var fib2 = fib;
    fib = function (i) { console.log("i:", i); return fib2(i); };
    
    function f(s) { console.log(s); }
    var C = (function () {
      function C() {
        this.f = function f(s) { f("Hello " + s); };
      }
      return C;
    })();
    new C().f("Alice");

    In case 1, the fib inside of the arrow function ends up pointing at the name fib we might give to the function expression, rather than the outer fib variable.

    In case 2, the f we call ends up invoking the function expression we assigned to the field f, rather than the name in the outer scope.

  15. ljharb commented on Oct 5, 2021

    @ljharb
    Contributor

    When it was standardized isn't important; it was present in every version of every web browser except IE, and it's web reality that code expects it to work. It's also polyfillable in IE 9+, with https://npmjs.com/function.prototype.name, and there's lots of websites that rely on that working, including airbnb.com (when i last checked, at least).

  16. DanielRosenwasser commented on Oct 7, 2021

    @DanielRosenwasser
    Member

    Every product will have a set of tradeoffs it has to make, and prioritizing the work to do this "by the book" is work with a likely tail of bugs and emit size implications. I can't speak for others who are on this issue, but I tend to agree with Ron that the people who would benefit from this have shrunk a lot as a group. For example:

    there's lots of websites that rely on that working, including airbnb.com (when i last checked, at least).

    Windows 10 (released late July 2015) actually makes it hard to access IE11. In fact, it will automatically punt you to Edge unless you disable a setting within Edge. But for the sake of it, here's Airbnb running in IE11:

    abnb-ie11.mp4
  17. ljharb commented on Oct 7, 2021

    @ljharb
    Contributor

    Daniel Rosenwasser (@DanielRosenwasser) that's a totally reasonable assessment; my pushback was against trying to justify not doing the work as a result of when something was standardized.

    Regardless of what's done by default, why not offer this as a config option, for those who want to emit correct code?

  18. sandersn commented on Dec 3, 2021

    @sandersn
    Member

    This is not practical to fix: the costs far outweigh the benefits.

  19. ljharb commented on Dec 3, 2021

    @ljharb
    Contributor

    Nathan Shively-Sanders (@sandersn) even behind an option?

  20. locked as resolved and limited conversation to collaborators on Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptES6Relates to the ES6 Spec

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions