evanw/esbuild · error · LexerPanic

For loop initializers cannot start with "async of"

Error message

For loop initializers cannot start with "async of"

What it means

Per tc39/ecma262#2034, 'for (async of ...)' is ambiguous with an async arrow function and is explicitly disallowed as a for-of initializer unless it is 'for await'. esbuild detects the token sequence 'async' then 'of' in a for-loop initializer and, when it is neither an arrow nor a for-await loop, errors out.

Source

Thrown at internal/js_parser/js_parser.go:3031

					needsAsyncLoc: asyncRange.Loc,
				})}
			}

		// "async x => {}"
		case js_lexer.TIdentifier:
			if level <= js_ast.LAssign {
				isArrowFn := true
				if (flags&exprFlagForLoopInit) != 0 && p.lexer.Identifier.String == "of" {
					// See https://github.com/tc39/ecma262/issues/2034 for details

					// "for (async of" is only an arrow function if the next token is "=>"
					isArrowFn = p.checkForArrowAfterTheCurrentToken()

					// Do not allow "for (async of []) ;" but do allow "for await (async of []) ;"
					if !isArrowFn && (flags&exprFlagForAwaitLoopInit) == 0 && p.lexer.Raw() == "of" {
						r := logger.Range{Loc: asyncRange.Loc, Len: p.lexer.Range().End() - asyncRange.Loc.Start}
						p.log.AddError(&p.tracker, r, "For loop initializers cannot start with \"async of\"")
						panic(js_lexer.LexerPanic{})
					}
				} else if p.options.ts.Parse && p.lexer.Token == js_lexer.TIdentifier {
					// Make sure we can parse the following TypeScript code:
					//
					//   export function open(async?: boolean): void {
					//     console.log(async as boolean)
					//   }
					//
					// TypeScript solves this by using a two-token lookahead to check for
					// "=>" after an identifier after the "async". This is done in
					// "isUnParenthesizedAsyncArrowFunctionWorker" which was introduced
					// here: https://github.com/microsoft/TypeScript/pull/8444
					isArrowFn = p.checkForArrowAfterTheCurrentToken()
				}

				if isArrowFn {
					p.markAsyncFn(asyncRange, false)
					ref := p.storeNameInRef(p.lexer.Identifier)

View on GitHub (pinned to f6058f8364)

Solutions

  1. Rename the loop variable away from 'async' (e.g. 'for (const x of items) {}').
  2. If async iteration was intended, use 'for await (const x of items) {}'.
  3. Use a different loop construct (for-index, forEach).

Example fix

// before
for (async of items) {}
// after
for (const item of items) {}
Defensive patterns

Strategy: validation

Validate before calling

import "github.com/evanw/esbuild/pkg/api"
res := api.Transform(src, api.TransformOptions{Loader: api.LoaderJS})
for _, m := range res.Errors {
  if strings.Contains(m.Text, `cannot start with "async of"`) {
    // m.Location marks the 'async' token; ask user to rename the binding
  }
}

Type guard

// Flag a for-of loop whose binding is the bare identifier 'async'.
var reForAsyncOf = regexp.MustCompile(`(?m)\\sfor\\s*\\(\\s*async\\s+of\\b`)
func hasForAsyncOf(src string) bool { return reForAsyncOf.MatchString(src) }

Prevention

When it happens

Trigger: Writing 'for (async of items) {}' (loop variable literally named 'async' in a for-of) without 'await'.

Common situations: Naming a loop binding 'async'; code generators that emit 'for (async of ...)'; confusion between async iteration and an 'async' variable name.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/ef4664a6cfec281d. Report an issue: GitHub.