Search code examples
javascriptlabeled-statements

Labelled functon statement - Undefined label


I look at spec and I can't understand why this code cause an error:

L: function a(){
  console.log(1);
  break L; /// Uncaught SyntaxError: Undefined label 'L'
  console.log(2);
}

a(); 

In the same time with code blocks it works well:

M: {
  console.log(1);
  break M;
  console.log(2)
}

By the way I can just mark function by label without using that label inside function. And I won't get an error:

L: function a(){
  console.log(1);
  console.log(2);
}

a(); 

Main question: What is the main purpose of labelled function statements if we can't use label inside function?


Solution

  • TLDR

    By the mid-1990s goto was frowned-upon as being "dangerous" and confusing and was thus elided from new languages. But abruptly exiting or "jumping" out of block constructs (blocks, try, with, if, loop constructs) was still a desirable feature, and so labels and break/continue were included as a kind of watered-down replacement.

    Note how labels and break/continue do not enable the looping behavior of the more powerful goto: they only allow you to effectively jump forward, out of a block.

    Functions are also block-like constructs. According to the spec, functions were not statements and therefore could not be labelled, but standards were seen as mere flexible guidelines during the early years of the Web; thus browser vendors inconsistently enabled the labelling of functions ("labelled function statements") [which would provide an alternative syntax to return?] This behavior was not in the original standard, but was backwards-standardized in ES2015 to throw an error if a labelled function statement was referenced by break <label>/continue <label>, although a labelled function statement without a reference to the label would be allowed for backwards compatibility reasons.

    This explains the behavior you observe.

    Details

    According to the spec, you can label all statements in JavaScript. This has been so since ECMAScript version 2.0, when labels were added to the language.

    These are the kinds of statement in JS:

    BlockStatement, VariableStatement, EmptyStatement, ExpressionStatement, IfStatement, BreakableStatement, ContinueStatement, BreakStatement, ReturnStatement, WithStatement, LabelledStatement, ThrowStatement, TryStatement, DebuggerStatement.

    Note that blocks are statements.

    But labels are only useable with break and continue, and so labelling some of these statements (eg. Variable, Return, Throw), while permitted, is pointless.

    This means that labels can be applied to constructs with which break and continue cannot be used, making labels in those positions de facto only useable as pseudo-comments.

    The language was designed this way because it simplified engine development by avoiding the need for "dead label analysis".

    "Function statements" were functions "in the statement position". ECMAScript 5.1 had the following to say:

    Several widely used implementations of ECMAScript are known to support the use of FunctionDeclaration as a Statement. However there are significant and irreconcilable variations among the implementations in the semantics applied to such FunctionDeclarations. Because of these irreconcilable differences, the use of a FunctionDeclaration as a Statement results in code that is not reliably portable among implementations. It is recommended that ECMAScript implementations either disallow this usage of FunctionDeclaration or issue a warning when such a usage is encountered. Future editions of ECMAScript may define alternative portable means for declaring functions in a Statement context.

    This means that in some older browsers function "declarations" could sometimes be treated as a statement, and therefore they could be labelled. What the precise behavior of historical browsers was with labelled functions I do not know. Modern browsers raise a syntax error if you attempt to refer to the label, as you found.

    So the following (a try statement) is valid JS:

    let foo = 'a'
    myLabel: try {
        if(foo === 'a') break myLabel
        console.log('this will not be printed')
    } catch {}
    console.log('all done')

    ...and the following (switch statement) is valid:

    let foo = 'a'
    myLabel: switch(foo) {
      case 'a': 
        break myLabel
        console.log('this will not be printed')
    }
    console.log('all done.')

    But also, the following (return statement) is valid (but pointless):

    function foo() {
      myLabel: return 'result of foo'
    }
    
    console.log(foo())

    ...and the labelling of this variable statement is pointless too:

    myLabel: var foo = 'value of foo'
    
    console.log('works fine, but the label was pointless')