165

Why does this work in a Node.js console (tested in 4.1.1 and 5.3.0), but doesn't work in the browser (tested in Chrome)?

This code block should create and invoke an anonymous function that logs Ok.

() => {
  console.log('Ok');
}()

Also, while the above works in Node.js, this does not work:

n => {
  console.log('Ok');
}()

Nor this:

(n) => {
  console.log('Ok');
}()

It is odd that when the parameter is added, it actually throws a SyntaxError at the immediately-invoking part.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
XCS
  • 23,776
  • 24
  • 82
  • 135

4 Answers4

218

You need to make it a function expression instead of function definition which doesn't need a name and makes it a valid JavaScript.

(() => {
  console.log('Ok');
})()

Is the equivalent of IIFE

(function(){
   console.log('Ok')
})();

And the possible reason why this works in Node.js but not in Chrome is because its parser interprets it as a self executing function, as this

function() { console.log('hello'); }();

works fine in Node.js. This is a function expression, and Chrome and Firefox and most of the browser interprets it this way. You need to invoke it manually.

The most widely accepted way to tell the parser to expect a function expression is just to wrap it in parens, because in JavaScript, parens can’t contain statements. At this point, when the parser encounters the function keyword, it knows to parse it as a function expression and not a function declaration.

Regarding the parametrized version, this will work.

((n) => {
  console.log('Ok');
})()
Null
  • 1,940
  • 9
  • 24
  • 29
void
  • 33,471
  • 8
  • 45
  • 91
  • 4
    The first example works in `Node.js` and actually logs the value. My question is why does it work? And why it doesn't when I add the parameter? – XCS Jan 04 '16 at 10:54
  • 1
    I am pretty familiar with `IIFE`s and know how to fix my code. I was just curious why, for example, my `IIFE` doesn't work when the `n` parameter is added, even though it worked without the parameter. – XCS Jan 04 '16 at 11:07
  • 3
    I didn't downvote, but the question is why does the parameterized version not work in Node when the exact same definition without a parameter does - it's not asking the difference between Node/Chrome implementations of anonymous functions – CodingIntrigue Jan 04 '16 at 11:07
  • 1
    Its a good-to-know but it doesn't answer the question, as mentioned before , - *why does the parameterized version not work in Node when the exact same definition without a parameter does* – jkris Sep 23 '16 at 02:55
  • But what is the equivalent of `function(){}()` in arrow functions? If I want the function object exposed function expressions protect against that. – dabadaba Dec 04 '19 at 11:07
  • @void Thank u =D – stan chacon Feb 04 '20 at 18:37
18

None of these should work without parentheses.

Why?

Because according in the spec:

  1. ArrowFunction is listed under AssignmentExpression
  2. The LHS of a CallExpression must be a MemberExpression, SuperCall or CallExpression

So an ArrowFunction cannot be on the LHS of a CallExpression.


What this effectively means in how => should be interpreted, is that it works on the same sort of level as assignment operators =, += etc.

Meaning

  • x => {foo}() doesn't become (x => {foo})()
  • The interpreter tries to interpret it as x => ({foo}())
  • Thus it's still a SyntaxError
  • So the interpreter decides that the ( must have been wrong and throws a SyntaxError

There was a bug on Babel about it here, too.

Paul S.
  • 58,277
  • 8
  • 106
  • 120
  • Those are some valid points, but if I try to replace the first, working version, with: `() => ({console.log('Ok')}())` it no longer works. So it doesn't really interpret it that way. – XCS Jan 04 '16 at 11:27
  • @Cristy It is not a valid Arrow Function production. It thinks that you are trying to create an Object with Object literal (enclosed by parenthesis) and `console.log(...)` is not a valid key name. – thefourtheye Jan 04 '16 at 11:33
  • @Cristy: Yes, I think the interpretation part of the above (the "Meaning" bit) may not be quite correct, but the specification parts are as far as I can tell. It also fits the error that I get from V8: `SyntaxError: Unexpected token (` (pointing at the `(` in the `()` at the end, not the `(` in `console.log(...)`). – T.J. Crowder Jan 04 '16 at 11:35
  • @T.J.Crowder you're right, I'll strike that out as it changes the error message and what I'm trying to say isn't conveyed (i.e. the `(` caused the interpreter to give up after exhausting attempts to find a valid interpretation and goes "this must be wrong then"), which may be wrong anyway because I don't know how the interpreter is really written – Paul S. Jan 04 '16 at 12:03
  • I am wondering if it is not a valid token at this position, wouldn't it try to insert a semi colon? – thefourtheye Jan 04 '16 at 12:07
  • @thefourtheye `;();` does actually throw the same syntax error!.. just not if there is anything inside the parens – Paul S. Jan 04 '16 at 12:08
  • So adding parens around it makes it a... MemberExpression? – Drazen Bjelovuk Jul 04 '18 at 18:47
  • @thefourtheye Semicolons are only inserted at line breaks, at the end of a block/function/script, and after a `do{..}while(..)`. – Robert Oct 09 '20 at 21:50
2

The reason you're seeing problems like this is that the console itself tries to emulate the global scope of the context you're currently targeting. It also tries to capture return values from statements and expressions you write in the console, so that the show up as results. Take, for instance:

> 3 + 2
< 5

Here, it executes as though it were an expression, but you've written it as though it were a statement. In normal scripts, the value would be discarded, but here, the code must be internally mangled (like wrapping the entire statement with a function context and a return statement), which causes all sorts of weird effects, including the problems you're experiencing.

This is also one of the reasons why some bare ES6 code in scripts works fine but doesn't in Chrome Dev Tools console.

Try executing this in Node and Chrome console:

{ let a = 3 }

In Node or a <script> tag it works just fine, but in the console, it gives Uncaught SyntaxError: Unexpected identifier. It also gives you a link to the source in the form of VMxxx:1 which you can click to inspect the evaluated source, which shows up as:

({ let a = 3 })

So why did it do this?

The answer is that it needs to convert your code into an expression so that the result can be returned to the caller and displayed in the console. You can do this by wrapping the statement in parentheses, which makes it an expression, but it also makes the block above syntactically incorrect (an expression cannot have a block declaration).

The console does try to fix these edge cases by being smart about the code, but that's beyond the scope of this answer, I think. You can file a bug to see if that's something they'd consider fixing.

Here's a good example of something very similar:

https://stackoverflow.com/a/28431346/46588

The safest way to make your code work is to make sure it can be run as an expression and inspect the SyntaxError source link to see what the actual execution code is and reverse engineer a solution from that. Usually it means a pair of strategically placed parentheses.

In short: the console tries to emulate the global execution context as accurately as possible, but due to the limitations of interaction with the v8 engine and JavaScript semantics this is sometimes hard or impossible to solve.

Community
  • 1
  • 1
Klemen Slavič
  • 19,181
  • 3
  • 31
  • 42
  • 1
    That's the entire point, I care about the parameter, but it doesn't work with the parameter set. – XCS Jul 15 '16 at 09:30
  • OK, I see your point. The difference is in the way the Chrome Dev Tools console actually executes your code. I'll edit the answer to reflect this. – Klemen Slavič Jul 15 '16 at 18:39
1

I asked a question like this:

@getify I’ve this question: to produce an #IIFE pattern we use parans around a function declaration to transform it into a function expression and then invoke it. Now in arrow function IIFEs, why do we need parans?! Isn’t the arrow function already an expression by default?!

and this is the Kyle Simpson's answer:

an arrow function is an expr, but we need surrounding parens b/c of "operator precedence" (sorta), so that the final parens to invoke the arrow-IIFE apply to the entire function and not to just the last token of its body.

x => console.log(x)(4)    // trouble

vs

(x => console.log(x))(4)    // working

— getify (@getify) June 12, 2020

Frank Nocke
  • 7,493
  • 3
  • 58
  • 89
  • My question was why it worked on some compilers and not on others. – XCS Jun 12 '20 at 17:55
  • That's because different compilers behave different in some details, just like different browsers, which of course have different compilers – Ershad Qaderi Jun 14 '20 at 15:28
  • You are right, they do behave differently, but the JavaScript specs are the same for all of them. I was curious which one was right, what does the JS spec say about this case and especially how could it be that it works without argument but doesn't with argument. I was looking for a more technical response. – XCS Jun 14 '20 at 20:06
  • Your example is pretty obvious, in the first case it should indeed call `console.log(x)(4)`. – XCS Jun 14 '20 at 20:08
  • I'm just guessing here but I think it is very reasonable to explain it like this: in arrow function expressions when we don't use a parameter we must use the parens and that makes it very clear for the engine that this is an arrow function expression, but when we have a single parameter the the parens are arbitrary which might no be very clear that this a function and confuses the that engine, to resolve the confusion we have to put a pair of parens around the whole function expression – Ershad Qaderi Jun 15 '20 at 08:43