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
- Rename the loop variable away from 'async' (e.g. 'for (const x of items) {}').
- If async iteration was intended, use 'for await (const x of items) {}'.
- 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
- Avoid 'async' as a variable/loop-binding name.
- Lint with eslint (no-unused-labels / identifier rules) to flag keyword-like names.
- For async iteration use 'for await ... of'.
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
- Unexpected newline after "async"
- Unexpected newline before "=>"
- Invalid binding pattern
- Unexpected ":"
- Unexpected "..."
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/ef4664a6cfec281d.
Report an issue: GitHub.