JavaScriptを書いていると、一度は必ず目にするエラーが SyntaxError: Unexpected token です。このエラーは「JavaScriptエンジンが予期しない文字(トークン)に出会った」ことを意味し、コードの文法ミスが原因で発生します。
しかし、エラーメッセージだけでは原因がわかりにくいことが多く、初心者から実務経験者まで頭を悩ませるエラーの一つです。本記事では、Unexpected token エラーのすべてのパターンを体系的に整理し、それぞれの原因と解決方法をコード付きで徹底解説します。
この記事で学べること
- Unexpected token エラーメッセージの読み方とパターン一覧
- 括弧・ブレース・ブラケットの対応不足の検出と修正
- カンマ・セミコロンに関連するSyntaxErrorの原因
- 文字列リテラル・テンプレートリテラルの問題と解決
- JSON.parse()で発生するUnexpected tokenの原因と対策
- アロー関数・省略構文でのよくあるミスと正しい書き方
- 予約語・キーワードの誤用による構文エラー
- HTML内のJavaScriptで起こる特有の問題
- ES6+構文と古い環境の互換性問題
- fetch/APIレスポンスでのJSON解析エラー
- 実務で頻出するパターン10選と解決フローチャート
「Unexpected token」エラーとは?
SyntaxError: Unexpected token は、JavaScriptのパーサー(構文解析器)がコードを解析する際に、文法的に正しくない文字(トークン)を検出したときに発生するエラーです。
このエラーは実行前の構文解析段階で発生するため、コードは1行も実行されません。つまり、エラー箇所より前にある console.log() なども動作しないという特徴があります。
エラーメッセージの読み方
Unexpected token エラーにはいくつかのバリエーションがあります。それぞれの意味を正しく理解しましょう。
| エラーメッセージ |
意味 |
Unexpected token '(' |
予期しない箇所に ( がある |
Unexpected token '}' |
予期しない箇所に } がある(閉じブレースが余分) |
Unexpected token '.' |
予期しない箇所にドットがある |
Unexpected token ';' |
セミコロンが不正な位置にある |
Unexpected identifier |
予期しない識別子(変数名やキーワード)がある |
Unexpected string |
予期しない位置に文字列がある |
Unexpected number |
予期しない位置に数値がある |
Unexpected end of input |
ファイルの終端に達したが、まだ閉じられていないものがある |
Unexpected token < |
HTMLがJavaScriptとして解析されている |
Unexpected token '=' |
代入演算子が不正な位置にある |
Unexpected token ILLEGAL |
不正な文字(非表示文字・全角スペースなど)がある |
ポイント:エラーメッセージに含まれるトークン((、}、; など)は、そのトークン自体が間違っているのではなく、その位置にそのトークンがあることが問題です。真の原因はエラー箇所より前にあることが多いので注意しましょう。
エラーの発生タイミング
SyntaxErrorが他のエラー(TypeError、ReferenceErrorなど)と大きく異なるのは、コードの実行前に発生するという点です。
SyntaxError は実行前に発生する
// このコードは1行も実行されない
console.log('このログは表示されません');
console.log('ここもスキップされます');
// ここに構文エラーがある
const x = {;
// SyntaxError: Unexpected token ';'
// 上記の console.log も一切実行されない
これに対し、TypeErrorやReferenceErrorは実行時エラーなので、エラー行より前のコードは正常に動作します。
TypeError は実行時に発生する(比較)
// このログは表示される
console.log('ここは実行されます');
// ここで実行時エラーが発生
const obj = null;
obj.toString(); // TypeError: Cannot read properties of null
| 比較項目 |
SyntaxError |
TypeError / ReferenceError |
| 発生タイミング |
構文解析時(実行前) |
実行時 |
| 前のコードの実行 |
一切実行されない |
エラー行まで実行される |
| try-catchで捕捉 |
同じスクリプト内では不可 |
可能 |
| 主な原因 |
文法ミス |
値の型や変数の問題 |
注意:SyntaxErrorは同じ <script> タグ内の try-catch では捕捉できません。ただし、eval() や new Function() で動的に生成されたコードのSyntaxErrorは try-catch で捕捉可能です。
括弧・ブレースの対応不足
Unexpected token エラーの最も一般的な原因の一つが、括弧(())、ブレース({})、ブラケット([])の対応不足です。開き括弧と閉じ括弧の数が一致しない場合にエラーが発生します。
波括弧(ブレース){} の閉じ忘れ
関数やif文、ループなどのブロックで波括弧を閉じ忘れると、予期しない場所でエラーが発生します。エラーメッセージが示す行は、実際のミス箇所とは異なる場合が多いです。
NG: 波括弧の閉じ忘れ
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
// } <-- forループの閉じ括弧を忘れている
return total;
}
// SyntaxError: Unexpected token '}'
// エラーは最後の } の位置で報告されるが、
// 実際の原因は for ループの閉じ忘れ
OK: 修正版
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price;
} // forループの閉じ括弧を追加
return total;
}
ネストが深い場合の閉じ忘れ
if文やループが複数ネストされている場合、どのブロックの閉じ括弧が不足しているのか特定が難しくなります。
NG: ネストが深い場合の閉じ忘れ
function processData(data) {
if (data) {
for (const item of data) {
if (item.active) {
console.log(item.name);
// } <-- この閉じ括弧が抜けている
}
}
}
// SyntaxError: Unexpected end of input
ポイント:ネストが深いコードでの閉じ括弧の対応確認方法:
- VSCodeの「括弧のペアのカラー化」機能を有効にする
- カーソルを括弧に置くと対応する括弧がハイライトされる
Ctrl + Shift + \ で対応する括弧にジャンプできる
- インデントを正しく揃えることで視覚的に対応を確認できる
丸括弧(パーレン)() の対応ミス
関数呼び出しや条件式で丸括弧の対応が崩れるケースです。特に、複雑な条件式やメソッドチェーンで発生しやすくなります。
NG: 丸括弧の対応ミス
// パターン1: 条件式の括弧不足
if ((age >= 18) && (age <= 65) {
// SyntaxError: Unexpected token '{'
// if文の閉じ丸括弧が足りない
}
// パターン2: メソッドチェーンの括弧不足
arr.filter(x => x > 0.map(x => x * 2);
// SyntaxError: Unexpected token '.'
// filter() の閉じ括弧がない
OK: 修正版
// パターン1: 閉じ括弧を追加
if ((age >= 18) && (age <= 65)) {
// 正常動作
}
// パターン2: filter() の閉じ括弧を追加
arr.filter(x => x > 0).map(x => x * 2);
角括弧(ブラケット)[] の問題
配列リテラルやプロパティアクセスで角括弧の対応が崩れるケースです。
NG: 角括弧の対応ミス
// パターン1: 配列リテラルの閉じ忘れ
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
// ] <-- 外側の閉じ括弧を忘れている
console.log(matrix);
// SyntaxError: Unexpected identifier 'console'
// パターン2: 分割代入でのミス
const [a, b, c = arr;
// SyntaxError: Unexpected token '='
// 閉じ ] が抜けている
OK: 修正版
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]; // 閉じブラケット追加
const [a, b, c] = arr; // 閉じブラケット追加
余分な閉じ括弧
閉じ括弧が多すぎる場合も Unexpected token エラーになります。コピー&ペーストの際に余分な括弧が入り込むケースが多いです。
NG: 余分な閉じ括弧
function greet(name) {
return `Hello, ${name}!`;
}}
// SyntaxError: Unexpected token '}'
// 閉じ括弧が1つ多い
括弧の対応確認テクニック
括弧の対応を効率よく確認するためのテクニックをまとめます。
| 方法 |
説明 |
| エディタの括弧マッチング |
カーソルを括弧に置くと対応する括弧がハイライトされる(VSCode標準機能) |
| 括弧のカラー化 |
VSCodeの「Bracket Pair Colorization」で色分け表示 |
| インデント整形 |
Prettier や VSCode の「Format Document」で自動整形し、インデントのずれを確認 |
| 折りたたみ |
コードの折りたたみ機能で各ブロックを折りたたんで対応を確認 |
| ESLint |
リアルタイムで構文エラーを検出し、エディタ上に表示 |
カンマ・セミコロンの問題
カンマ(,)やセミコロン(;)の位置や有無による構文エラーも非常に多いパターンです。特にオブジェクトリテラル、配列、JSON、そしてJavaScriptの自動セミコロン挿入(ASI)に関連する問題を解説します。
オブジェクトリテラルでのカンマの問題
オブジェクトのプロパティ間にカンマが抜けている、または余分なカンマがあるとエラーになります。
NG: プロパティ間のカンマ抜け
const user = {
name: '山田太郎'
age: 30,
email: 'taro@example.com'
};
// SyntaxError: Unexpected identifier 'age'
// name の後にカンマが抜けている
OK: 修正版
const user = {
name: '山田太郎', // カンマを追加
age: 30,
email: 'taro@example.com'
};
配列でのカンマの問題
配列要素間のカンマ抜けや、意図しないカンマ(連続カンマ)による問題です。
NG: 配列のカンマの問題
// パターン1: 配列要素間のカンマ抜け
const colors = [
'red'
'green',
'blue'
];
// SyntaxError: Unexpected string
// パターン2: 関数引数のカンマ抜け
Math.max(10 20 30);
// SyntaxError: Unexpected number
OK: 修正版
const colors = [
'red', // カンマを追加
'green',
'blue'
];
Math.max(10, 20, 30); // カンマで区切る
末尾カンマ(Trailing Comma)の問題
JavaScriptではオブジェクトや配列の末尾カンマは許容されますが、JSONでは許可されていません。また、関数の引数の末尾カンマはES2017以降でサポートされています。
| コンテキスト |
末尾カンマ |
対応バージョン |
| 配列リテラル |
OK |
ES5以降 |
| オブジェクトリテラル |
OK |
ES5以降 |
| 関数の引数 |
OK |
ES2017以降 |
| JSON |
NG(エラー) |
– |
| import / export |
OK |
ES2015以降 |
末尾カンマの動作
// JavaScript では OK
const obj = {
a: 1,
b: 2, // 末尾カンマ OK
};
const arr = [1, 2, 3,]; // 末尾カンマ OK
// JSON では NG
const jsonStr = '{"a": 1, "b": 2,}';
JSON.parse(jsonStr);
// SyntaxError: Unexpected token '}' ... in JSON
自動セミコロン挿入(ASI)の罠
JavaScriptには自動セミコロン挿入(Automatic Semicolon Insertion、ASI)という仕組みがあり、省略されたセミコロンを自動的に補完します。しかし、ASIが意図しない動作を引き起こし、Unexpected token エラーの原因になることがあります。
ASI の罠: return文
// return と値の間で改行すると ASI が適用される
function getUser() {
return // ここで ASI がセミコロンを挿入
{
name: '太郎'
};
}
// 上記は以下のように解釈される
function getUser() {
return; // undefined を返す
{ // ブロック文として解釈
name: '太郎'
};
}
注意:この例ではSyntaxErrorにはなりませんが、undefinedが返されるという意図しない動作になります。return の後に改行を入れず、同じ行にオブジェクトの開始{を書くのが正しい書き方です。
OK: return文の正しい書き方
function getUser() {
return { // return と { を同じ行に
name: '太郎'
};
}
ASI の罠: 行頭の括弧
const a = 1
const b = 2
// ここが問題
(a + b).toString()
// ASI は上記を以下のように解釈する
const a = 1;
const b = 2(a + b).toString();
// 2 を関数として呼び出そうとしてエラーになる
// TypeError: 2 is not a function
ASI の罠: 行頭の配列
const x = 42
[1, 2, 3].forEach(n => console.log(n))
// ASI は上記を以下のように解釈する
const x = 42[1, 2, 3].forEach(n => console.log(n));
// 42[3] は undefined なので forEach が使えずエラー
ポイント:ASIの問題を防ぐベストプラクティス:
- 常にセミコロンを明示的に書く
- 行頭が
(、[、` で始まる場合は前の行にセミコロンを必ず付ける
- ESLintの
semi ルールでセミコロンの一貫性を保つ
- Prettierなどのフォーマッタを使用する
セミコロンの誤用による Unexpected token
セミコロンが不適切な位置にある場合もエラーになります。
NG: セミコロンの誤用
// パターン1: for文のセミコロン位置ミス
for (let i = 0, i < 10, i++) {
// SyntaxError: Unexpected token '<'
// カンマではなくセミコロンで区切る必要がある
}
// パターン2: オブジェクトプロパティにセミコロン
const config = {
host: 'localhost';
port: 3000;
};
// SyntaxError: Unexpected token ';'
// オブジェクトではセミコロンではなくカンマ
OK: 修正版
// for文はセミコロンで区切る
for (let i = 0; i < 10; i++) {
// OK
}
// オブジェクトはカンマで区切る
const config = {
host: 'localhost',
port: 3000
};
for文とfor…of / for…in の構文混同
for文の3つの構文を混同するとSyntaxErrorになります。
| 構文 |
区切り |
例 |
| for |
セミコロン(;) |
for (let i = 0; i < n; i++) |
| for…in |
in キーワード |
for (const key in obj) |
| for…of |
of キーワード |
for (const item of arr) |
文字列・テンプレートリテラルの問題
文字列リテラルやテンプレートリテラルに関連するSyntaxErrorは、クォートの閉じ忘れ、クォートの混在、エスケープ漏れが主な原因です。特に文字列内にクォート文字自体を含める場合に問題が起きやすくなります。
クォートの閉じ忘れ
文字列を開始したクォートを閉じ忘れると、それ以降のコードがすべて文字列の一部として解釈され、予期しないトークンエラーが発生します。
NG: クォートの閉じ忘れ
// パターン1: シングルクォートの閉じ忘れ
const message = 'Hello World;
// SyntaxError: Invalid or unexpected token
// パターン2: ダブルクォートの閉じ忘れ
const name = "山田太郎;
// SyntaxError: Invalid or unexpected token
OK: 修正版
const message = 'Hello World';
const name = "山田太郎";
文字列内のクォート文字
文字列内に同じ種類のクォート文字を含める場合、エスケープが必要です。これを忘れるとSyntaxErrorになります。
NG: 文字列内のクォートが未エスケープ
// シングルクォート内にアポストロフィ
const msg = 'It's a beautiful day';
// SyntaxError: Unexpected identifier 's'
// ダブルクォート内にダブルクォート
const html = "<div class="container">";
// SyntaxError: Unexpected identifier 'container'
OK: 4つの解決方法
// 方法1: エスケープする
const msg1 = 'It\'s a beautiful day';
// 方法2: 異なるクォートで囲む
const msg2 = "It's a beautiful day";
// 方法3: テンプレートリテラルを使う
const msg3 = `It's a beautiful day`;
// 方法4: HTML属性のクォート
const html = '<div class="container">';
| 方法 |
使いどころ |
注意点 |
| バックスラッシュエスケープ |
同じクォート内に同種のクォートを含む |
読みにくくなりやすい |
| 異なるクォートで囲む |
文字列内に片方のクォートしかない |
チームの規約に合わせる |
| テンプレートリテラル |
変数埋め込みや複数行が必要な場合 |
バッククォート自体を含む場合はエスケープ |
| HTML属性で使い分け |
HTML文字列を構築する場合 |
外側と内側で異なるクォートを使う |
テンプレートリテラルの問題
テンプレートリテラル(バッククォート `)特有の問題もあります。
NG: テンプレートリテラルの問題
// パターン1: ${ の閉じ忘れ
const greeting = `Hello, ${name!`;
// SyntaxError: Unexpected token '!'
// パターン2: ネストしたテンプレートリテラル
const html = `<div class="${isActive ? `active` : ``}">`;
// バッククォートのネストは OK だが、混乱しやすい
// パターン3: 通常のクォートで ${ を使おうとする
const msg = "Hello, ${name}";
// エラーにはならないが、リテラル文字列 "${name}" になる
OK: 修正版
// ${ } を正しく閉じる
const greeting = `Hello, ${name}!`;
// ネストしたテンプレートリテラル
const html = `<div class="${isActive ? 'active' : '}">`;
// テンプレートリテラルにはバッククォートを使う
const msg = `Hello, ${name}`;
改行を含む文字列
通常のクォート(シングル・ダブル)では改行を含む文字列を直接記述できません。テンプレートリテラルを使うか、エスケープシーケンスを使います。
NG: 通常クォートで改行
const text = "1行目
2行目
3行目";
// SyntaxError: Invalid or unexpected token
OK: 改行を含む文字列の正しい書き方
// 方法1: テンプレートリテラル(推奨)
const text1 = `1行目
2行目
3行目`;
// 方法2: \n エスケープシーケンス
const text2 = "1行目\n2行目\n3行目";
// 方法3: 文字列連結
const text3 = "1行目\n" +
"2行目\n" +
"3行目";
全角文字によるエラー
日本語入力中にクォートが全角文字に変わってしまうケースがあります。見た目は似ていますが、JavaScriptは全角のクォートを認識しません。
NG: 全角文字の混入
// 全角クォート(見た目では判別しにくい)
const msg = “Hello”; // “ ” は全角
// SyntaxError: Unexpected token ILLEGAL
// 全角スペースの混入
const x = 10; // const と x の間が全角スペース
// SyntaxError: Unexpected token ILLEGAL
// 全角セミコロン
const y = 20; // 全角セミコロン
// SyntaxError: Unexpected token ILLEGAL
ポイント:全角文字の検出方法:
- VSCodeの「全角スペースをハイライト」拡張機能を使う
- ESLintの
no-irregular-whitespace ルールで検出
- エディタの「不可視文字を表示」オプションを有効にする
- テキストをコピー&ペーストした後は特に注意する
JSON.parse() でのエラー
JSON.parse() は厳密なJSON仕様に従って文字列を解析するため、JavaScriptのオブジェクトリテラルでは許容される書き方でもエラーになることがあります。JSONパースで発生する Unexpected token エラーは、実務で最も頻繁に遭遇するパターンの一つです。
JSON仕様とJavaScriptオブジェクトの違い
JSONとJavaScriptのオブジェクトリテラルには重要な違いがあります。
| 項目 |
JavaScript オブジェクト |
JSON |
| キー名のクォート |
省略可能 |
ダブルクォート必須 |
| 文字列のクォート |
シングル / ダブル / バッククォート |
ダブルクォートのみ |
| 末尾カンマ |
許可 |
不許可 |
| コメント |
許可(// や /* */) |
不許可 |
| undefined |
使用可能 |
不許可 |
| 関数 |
プロパティとして設定可能 |
不許可 |
よくあるJSON.parseエラーパターン
NG: シングルクォートのキー/値
// シングルクォートは JSON では無効
const jsonStr = "{ 'name': '太郎' }";
JSON.parse(jsonStr);
// SyntaxError: Unexpected token '\'', ... in JSON
OK: ダブルクォートを使用
const jsonStr = '{ "name": "太郎" }';
JSON.parse(jsonStr); // OK
NG: 末尾カンマ
const jsonStr = '{"a": 1, "b": 2,}';
JSON.parse(jsonStr);
// SyntaxError: Unexpected token '}'
NG: コメント入りのJSON
const jsonStr = `{
// これはコメント
"name": "太郎"
}`;
JSON.parse(jsonStr);
// SyntaxError: Unexpected token '/'
NG: キー名にクォートがない
const jsonStr = '{ name: "太郎" }';
JSON.parse(jsonStr);
// SyntaxError: Unexpected token 'n'
HTMLレスポンスをJSON.parseしようとするケース
APIからのレスポンスがJSONではなくHTMLだった場合、JSON.parse() で Unexpected token '<' エラーが発生します。
NG: HTMLをJSONとしてパースしようとする
// サーバーがHTMLエラーページを返した場合
const response = '<!DOCTYPE html><html>...';
JSON.parse(response);
// SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
OK: パース前にレスポンスを検証する
function safeJsonParse(str) {
try {
return JSON.parse(str);
} catch (e) {
console.error('JSON parse failed:', e.message);
console.error('Raw string:', str.substring(0, 100));
return null;
}
}
// 使い方
const data = safeJsonParse(responseText);
if (data !== null) {
// 正常にパースできた場合の処理
}
JSON.parse のデバッグテクニック
JSON.parseエラーが発生した際のデバッグ手順を紹介します。
JSON.parse デバッグテクニック
// ステップ1: パース前に文字列を確認
console.log('Type:', typeof str);
console.log('First 200 chars:', str.substring(0, 200));
console.log('Length:', str.length);
// ステップ2: 空文字列や undefined でないか確認
if (!str || str.trim() === '') {
console.error('Empty string');
return;
}
// ステップ3: HTMLかどうか確認
if (str.trim().startsWith('<')) {
console.error('Response is HTML, not JSON');
return;
}
// ステップ4: JSONバリデータで検証
// https://jsonlint.com/ などのオンラインツールを使用
ポイント:JSON.parse()のエラーメッセージには、問題のある文字の位置(position)が含まれています。SyntaxError: Unexpected token ... at position 42 のように表示されるので、文字列の42文字目付近を確認しましょう。
二重パースの問題
すでにパースされたオブジェクトを再度 JSON.parse() しようとするとエラーになります。
NG: 二重パース
const data = { name: '太郎' }; // すでにオブジェクト
JSON.parse(data); // オブジェクトをパースしようとする
// SyntaxError: Unexpected token 'o'
// data.toString() => "[object Object]" をパースしようとして失敗
OK: パース前に型チェック
function ensureObject(data) {
if (typeof data === 'string') {
return JSON.parse(data);
}
return data; // すでにオブジェクトならそのまま返す
}
アロー関数・省略構文のミス
ES6で導入されたアロー関数は簡潔な構文が魅力ですが、その省略形にはいくつかの落とし穴があります。特にオブジェクトリテラルを返す場合や省略構文の限界を理解していないとSyntaxErrorが発生します。
オブジェクトリテラルの返却
アロー関数でオブジェクトリテラルを返そうとすると、波括弧がブロック文として解釈されるため、SyntaxErrorになります。
NG: オブジェクトリテラルの返却ミス
// { } がブロック文として解釈される
const getUser = () => { name: '太郎', age: 30 };
// SyntaxError: Unexpected token ':'
// JavaScript は上記を以下のように解釈する
// () => { name: "太郎" } ... name はラベル文、"太郎" は式文
// , age: 30 でカンマ演算子 + age ラベルとなり構文エラー
OK: 丸括弧で囲む
// 方法1: () で囲む(推奨)
const getUser = () => ({ name: '太郎', age: 30 });
// 方法2: return文を明示する
const getUser2 = () => {
return { name: '太郎', age: 30 };
};
注意:この問題は配列メソッド(map、reduceなど)で特に頻発します。arr.map(item => ({ id: item.id, name: item.name })) のように ({ }) で囲むことを忘れないでください。
map / filter / reduce でのアロー関数
配列メソッドのコールバックでアロー関数を使う際のよくあるミスを紹介します。
NG: map でオブジェクトを返すミス
const users = ['Alice', 'Bob', 'Charlie'];
// NG: ブロック文として解釈される
const result = users.map(name => { name: name, active: true });
// SyntaxError: Unexpected token ':'
OK: 修正版
const users = ['Alice', 'Bob', 'Charlie'];
// OK: () で囲む
const result = users.map(name => ({ name: name, active: true }));
// [{ name: "Alice", active: true }, ...]
// ES6 のショートハンドプロパティも使える
const result2 = users.map(name => ({ name, active: true }));
アロー関数の構文ミス
アロー関数自体の構文ミスもSyntaxErrorの原因になります。
NG: アロー関数の構文ミス集
// パターン1: => の書き間違い
const add = (a, b) = > a + b; // = と > の間にスペース
// SyntaxError: Unexpected token '>'
// パターン2: 複数引数で括弧を省略
const add2 = a, b => a + b;
// SyntaxError: Unexpected token ','
// 引数が2つ以上の場合は () が必要
// パターン3: 通常の関数と混同
const greet = function => { return 'Hello'; };
// SyntaxError: Unexpected token '=>'
OK: 修正版
// => はスペースなしで書く
const add = (a, b) => a + b;
// 複数引数は括弧で囲む
const add2 = (a, b) => a + b;
// function キーワードとアローは混ぜない
const greet = () => 'Hello';
// または
const greet2 = function() { return 'Hello'; };
アロー関数の省略構文まとめ
| パターン |
省略可能な部分 |
例 |
| 引数1つ |
引数の() |
x => x * 2 |
| 引数なし |
省略不可 |
() => 42 |
| 引数2つ以上 |
省略不可 |
(a, b) => a + b |
| 式を返す |
{}とreturn |
x => x * 2 |
| オブジェクトを返す |
()で囲む必要あり |
x => ({ id: x }) |
| 複数文 |
省略不可 |
x => { ...; return y; } |
分割代入パラメータでのミス
アロー関数の引数で分割代入を使う場合のよくあるミスです。
NG: 分割代入パラメータの括弧忘れ
// 分割代入の引数は () で囲む必要がある
const getName = { name } => name;
// SyntaxError: Unexpected token '=>'
OK: 修正版
// 分割代入パラメータは () で囲む
const getName = ({ name }) => name;
// 配列の分割代入も同様
const getFirst = ([first]) => first;
// デフォルト値付き
const getInfo = ({ name = 'anonymous', age = 0 }) => `${name} (${age})`;
予約語・キーワードの誤用
JavaScriptには予約語(reserved words)があり、これらを変数名や関数名として使用するとSyntaxErrorが発生します。また、将来の仕様のために予約されている語もあります。
予約語を変数名に使用
NG: 予約語を変数名に使用
// class は予約語
const class = 'A';
// SyntaxError: Unexpected token 'class'
// return は予約語
const return = 42;
// SyntaxError: Unexpected token 'return'
// import は予約語
const import = 'module';
// SyntaxError: Unexpected token 'import'
OK: 予約語を避ける命名
const className = 'A';
const returnValue = 42;
const importPath = 'module';
JavaScriptの予約語一覧
| カテゴリ |
予約語 |
| 制御構文 |
if, else, switch, case, default, for, while, do, break, continue, return |
| 宣言 |
var, let, const, function, class |
| 例外 |
try, catch, finally, throw |
| モジュール |
import, export |
| その他 |
new, delete, typeof, void, in, instanceof, this, super, with, yield, debugger |
| 将来予約 |
enum, await(モジュール外), implements, interface, package, private, protected, public(strict mode) |
オブジェクトプロパティとしての予約語
ES5以降では、オブジェクトのプロパティ名として予約語を使用することは許可されています。ただし、変数名としては使えません。
予約語のプロパティ名は OK
// プロパティ名としては OK
const obj = {
class: 'A',
return: 42,
for: 'loop'
};
// ドット表記でアクセス可能
console.log(obj.class); // "A"
console.log(obj.return); // 42
// 分割代入する場合はリネームが必要
const { class: className } = obj; // OK
// const { class } = obj; // SyntaxError
strictモードでの追加制限
"use strict" や ESモジュールでは、追加の予約語が使用禁止になります。
strictモードの追加制限
"use strict";
// strict モードでは以下が禁止
const implements = 1; // SyntaxError
const interface = 2; // SyntaxError
const package = 3; // SyntaxError
const private = 4; // SyntaxError
const protected = 5; // SyntaxError
const public = 6; // SyntaxError
const static = 7; // SyntaxError
// eval と arguments も変数名として使えない
const eval = 8; // SyntaxError
const arguments = 9; // SyntaxError
await と async の問題
awaitはasync関数の外で使用するとエラーになります(トップレベルawaitを除く)。
NG: async 関数外での await
// 通常の関数内で await を使用
function fetchData() {
const response = await fetch('/api/data');
// SyntaxError: Unexpected identifier 'fetch'
// または: await is only valid in async functions
}
OK: async 関数内で await を使用
// async を付ける
async function fetchData() {
const response = await fetch('/api/data');
return response;
}
// アロー関数の場合
const fetchData2 = async () => {
const response = await fetch('/api/data');
return response;
};
HTML内のJavaScriptでのエラー
HTMLファイル内の <script> タグで実行するJavaScriptには、単体のJSファイルにはない特有の問題があります。scriptタグの属性ミスやHTMLとの干渉がSyntaxErrorの原因になります。
scriptタグの type 属性の問題
<script> タグの type 属性が正しく設定されていないと、ブラウザがコードを正しく解釈できません。
NG: type属性のミス
<!-- type="text/javascript" のタイプミス -->
<script type="text/javasript">
// コードが実行されない(無視される)
</script>
<!-- type="module" なしで import を使用 -->
<script>
import { utils } from './utils.js';
// SyntaxError: Cannot use import statement outside a module
</script>
OK: 正しいscriptタグの使い方
<!-- 通常のJavaScript(type 省略可能)-->
<script>
console.log('Hello');
</script>
<!-- ES Modules を使う場合 -->
<script type="module">
import { utils } from './utils.js';
</script>
HTML内での < や > の干渉
HTML内のJavaScriptで比較演算子を使用すると、ブラウザのHTMLパーサーがHTMLタグの開始と誤解する場合があります。
注意: HTML内の </script> 文字列
<script>
// NG: 文字列内の </script> がscriptタグの終了と解釈される
const html = '<script>alert("Hi")</script>';
// HTML パーサーが </script> を検出してスクリプトが終了してしまう
</script>
OK: エスケープする
<script>
// 方法1: 文字列を分割
const html = '<script>alert("Hi")</scr' + 'ipt>';
// 方法2: エスケープ
const html2 = '<script>alert("Hi")<\/script>';
</script>
外部ファイルの読み込みミス
scriptタグで外部JSファイルを読み込む際のよくあるミスです。
NG: 外部ファイルの読み込みミス
<!-- NG: src属性とインラインコードの両方がある -->
<script src="app.js">
console.log('This will be ignored');
</script>
<!-- NG: パスが間違っている(404でHTMLエラーページが返される)-->
<script src="appp.js"></script>
<!-- SyntaxError: Unexpected token '<' -->
<!-- 404エラーページ(HTML)がJSとして解析される -->
OK: 正しい外部ファイル読み込み
<!-- 外部ファイルとインラインコードは別々の script タグに -->
<script src="app.js"></script>
<script>
console.log('This runs after app.js');
</script>
ポイント:Unexpected token '<' エラーが発生した場合は、まずブラウザの開発者ツール > Network タブで、JSファイルのレスポンスがHTMLになっていないか確認しましょう。404エラーページやリダイレクト先のHTMLページが返されているケースが多いです。
DOMContentLoaded と実行タイミング
直接的なSyntaxErrorではありませんが、DOM要素がまだ読み込まれていない状態でアクセスしようとするとエラーが発生します。
OK: スクリプトの実行タイミング制御
<!-- 方法1: defer 属性(推奨)-->
<script src="app.js" defer></script>
<!-- 方法2: DOMContentLoaded イベント -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// DOM が読み込まれた後に実行
const el = document.getElementById('app');
});
</script>
<!-- 方法3: body の最後に配置 -->
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
ES6+構文と古い環境の互換性問題
ES6(ES2015)以降の新しいJavaScript構文を、対応していない古いブラウザや実行環境で実行すると、SyntaxErrorが発生します。特にOptional Chaining(?.)、Nullish Coalescing(??)、プライベートフィールド(#)などの比較的新しい構文で問題が起きます。
Optional Chaining(?.)の未サポート
Optional Chaining(?.)はES2020で導入されました。古いブラウザやNode.js 14未満では使用できません。
古い環境でのエラー
// Optional Chaining(ES2020)
const name = user?.profile?.name;
// 古い環境: SyntaxError: Unexpected token '.'
// Nullish Coalescing(ES2020)
const value = input ?? 'default';
// 古い環境: SyntaxError: Unexpected token '?'
// Optional Chaining + メソッド呼び出し
obj.method?.();
// 古い環境: SyntaxError: Unexpected token '.'
代替コード(古い環境向け)
// Optional Chaining の代替
const name = user && user.profile && user.profile.name;
// Nullish Coalescing の代替
const value = input !== null && input !== undefined ? input : 'default';
// または(falsy な値も含む場合)
const value2 = input || 'default';
ES6+構文のブラウザ対応状況
| 構文 |
仕様 |
Chrome |
Firefox |
Node.js |
| アロー関数 |
ES2015 |
45+ |
22+ |
4+ |
| テンプレートリテラル |
ES2015 |
41+ |
34+ |
4+ |
| let / const |
ES2015 |
49+ |
44+ |
6+ |
| 分割代入 |
ES2015 |
49+ |
41+ |
6+ |
| async / await |
ES2017 |
55+ |
52+ |
7.6+ |
| Optional Chaining (?.) |
ES2020 |
80+ |
72+ |
14+ |
| Nullish Coalescing (??) |
ES2020 |
80+ |
72+ |
14+ |
| Logical Assignment (??=) |
ES2021 |
85+ |
79+ |
15+ |
| Private Fields (#) |
ES2022 |
74+ |
90+ |
12+ |
Babel によるトランスパイル
古い環境をサポートする必要がある場合は、Babelを使用してES6+構文をES5に変換できます。
Babel の設定例
// .babelrc または babel.config.json
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions"]
}
}]
]
}
Babel によるトランスパイル結果の例
// 変換前(ES2020+)
const name = user?.profile?.name ?? 'anonymous';
// 変換後(ES5互換)
var _user$profile;
var _user$profile$name;
var name = (_user$profile$name =
(_user$profile = user) === null ||
_user$profile === void 0 ? void 0 :
(_user$profile = _user$profile.profile) === null ||
_user$profile === void 0 ? void 0 :
_user$profile.name) !== null &&
_user$profile$name !== void 0 ?
_user$profile$name : 'anonymous';
ポイント:最新のブラウザを対象にしている場合は、Babelによるトランスパイルは不要です。browserslistの設定で対象ブラウザを明確にし、不要なトランスパイルを避けることでバンドルサイズを削減できます。Can I useで対応状況を確認しましょう。
Node.jsのバージョンによる問題
Node.jsのバージョンによって対応する構文が異なります。プロジェクトの.node-versionや.nvmrcで適切なバージョンを指定しましょう。
Node.jsバージョン確認
# Node.jsバージョンを確認
node -v
# nvm でバージョンを切り替え
nvm use 18
# .nvmrc ファイルを作成
echo "18" > .nvmrc
fetch / APIレスポンスでのエラー
Web開発では、fetch() や XMLHttpRequest でAPIからデータを取得する際に SyntaxError: Unexpected token エラーが頻発します。ほとんどの場合、APIのレスポンスがJSONではないことが原因です。
fetch() の .json() でのエラー
response.json() は内部的に JSON.parse() を呼び出すため、レスポンスがJSON形式でない場合にエラーが発生します。
NG: レスポンスがHTMLの場合
// APIがHTMLエラーページを返した場合
const response = await fetch('/api/users');
const data = await response.json();
// SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
// よくある原因:
// 1. APIのURLが間違っている(404エラーページがHTMLで返る)
// 2. 認証が必要(ログインページにリダイレクト)
// 3. サーバーエラー(500エラーページがHTML)
// 4. CORSエラーでレスポンスが空
OK: レスポンスを事前に検証する
async function fetchJson(url) {
const response = await fetch(url);
// ステップ1: ステータスコードを確認
if (!response.ok) {
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
}
// ステップ2: Content-Type を確認
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
const text = await response.text();
console.error('Expected JSON but got:', text.substring(0, 200));
throw new Error('Response is not JSON');
}
// ステップ3: 安全にパース
return response.json();
}
よくある原因と確認方法
| 原因 |
症状 |
解決方法 |
| URLが間違っている |
404エラーページ(HTML)が返る |
URLを確認、Networkタブで確認 |
| 認証切れ |
ログインページにリダイレクト |
トークン/セッションを確認 |
| サーバーエラー |
500エラーページ(HTML)が返る |
サーバーログを確認 |
| CORSエラー |
空のレスポンス |
サーバー側のCORS設定を確認 |
| APIの仕様変更 |
予期しないレスポンス形式 |
API仕様書を再確認 |
| プロキシの問題 |
開発サーバーのプロキシが失敗 |
プロキシ設定を確認 |
開発者ツールでの確認方法
fetch/APIレスポンスのエラーを調査する際は、ブラウザの開発者ツールが最も効果的です。
開発者ツールでの調査手順
// 1. Network タブでリクエストを確認
// - ステータスコード(200? 404? 500?)
// - Response Headers の Content-Type
// - Response Body の実際の中身
// 2. コンソールでレスポンスを確認
const response = await fetch('/api/users');
console.log('Status:', response.status);
console.log('Content-Type:', response.headers.get('content-type'));
// テキストとして取得して確認
const text = await response.text();
console.log('Body:', text);
// 注意: response.text() と response.json() は一度しか呼べない
// 両方確認したい場合は clone() を使う
const resp = await fetch('/api/users');
const text2 = await resp.clone().text();
console.log('Raw:', text2);
const json = await resp.json(); // これでもOK
実務で役立つ堅牢なfetchラッパー
実務では以下のようなラッパー関数を用意しておくと、APIレスポンスのエラーを効率的にハンドリングできます。
堅牢なfetchラッパー関数
class ApiError extends Error {
constructor(status, statusText, body) {
super(`API Error: ${status} ${statusText}`);
this.status = status;
this.statusText = statusText;
this.body = body;
}
}
async function apiFetch(url, options = {}) {
const response = await fetch(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
...options.headers
},
...options
});
// ステータスコード確認
if (!response.ok) {
const body = await response.text();
throw new ApiError(response.status, response.statusText, body);
}
// Content-Type確認
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
const text = await response.text();
console.error('Non-JSON response:', text.substring(0, 200));
throw new Error(`Expected JSON, got ${contentType}`);
}
return response.json();
}
// 使い方
try {
const users = await apiFetch('/api/users');
console.log(users);
} catch (error) {
if (error instanceof ApiError) {
console.error(`API Error ${error.status}:``, error.body);
} else {
console.error('Unexpected error:', error.message);
}
}
実務でよくあるパターン10選
ここまで解説してきた内容を踏まえ、実務でよく遭遇するUnexpected tokenエラーのパターンを10個厳選して紹介します。それぞれの原因と解決方法を簡潔にまとめます。
パターン1: コピー&ペーストによる不可視文字の混入
Webサイトやドキュメントからコードをコピーすると、見えない特殊文字(ゼロ幅スペース、BOMなど)が混入することがあります。
不可視文字の検出と除去
// エラーの原因: 不可視文字が混入
// SyntaxError: Unexpected token ILLEGAL
// 対策1: エディタで不可視文字を表示する
// VSCode: "editor.renderControlCharacters": true
// 対策2: 問題のある行を削除して手動で再入力する
// 対策3: 文字列からBOMを除去
const cleanStr = str.replace(/^\uFEFF/, '');
// 対策4: ゼロ幅スペースを除去
const cleanStr2 = str.replace(/[\u200B-\u200D\uFEFF]/g, '');
パターン2: JSONファイルのコメント
package.json、tsconfig.jsonなどの設定ファイルにコメントを書いてしまうケースです。
JSONファイルのコメント問題
// NG: package.json にコメントを書く
{
"name": "my-app",
// これはコメント <-- NG!
"version": "1.0.0"
}
// OK: tsconfig.json は JSONC(JSON with Comments)なのでコメントOK
// OK: .eslintrc はJSファイル(.eslintrc.js)にすればコメントOK
パターン3: import 文のモジュール環境問題
import 文の環境問題
// NG: 非モジュール環境で import を使用
import { utils } from './utils.js';
// SyntaxError: Cannot use import statement outside a module
// 解決策1: HTML で type="module" を指定
// <script type="module" src="app.js"></script>
// 解決策2: Node.js で package.json に "type": "module" を追加
// { "type": "module" }
// 解決策3: Node.js で require を使う
const { utils } = require('./utils.js');
パターン4: 三項演算子のネスト
三項演算子のネストの問題
// NG: 括弧不足で予期しないトークン
const result = a > 0 ? 'positive' : a < 0 ? 'negative' 'zero';
// SyntaxError: Unexpected string
// 2つ目の条件分岐で : が抜けている
// OK: 正しいネスト
const result2 = a > 0 ? 'positive' : a < 0 ? 'negative' : 'zero';
パターン5: テンプレートリテラル内の式の問題
テンプレートリテラル内の式
// NG: テンプレートリテラル内のオブジェクト
console.log(`Result: ${{ key: value }}`);
// エラーにはならないが "[object Object]" と表示される
// OK: JSON.stringify を使う
console.log(`Result: ${JSON.stringify({ key: value })}`);
パターン6: 正規表現のスラッシュ問題
正規表現のスラッシュ問題
// NG: 正規表現が除算と誤解される場合
const x = 42
/pattern/.test('text')
// ASIにより: 42 / pattern / .test("text")
// と解釈されてエラー
// OK: セミコロンを入れる
const x2 = 42;
/pattern/.test('text');
パターン7: switch文のcaseでの変数宣言
switch文のcaseでの変数宣言
// NG: case内で直接 let/const を使うとエラーになる場合がある
switch (action) {
case 'add':
const item = createItem();
break;
case 'remove':
const item = findItem(); // SyntaxError: Identifier 'item' has already been declared
break;
}
// OK: ブロックスコープを作る
switch (action) {
case 'add': {
const item = createItem();
break;
}
case 'remove': {
const item = findItem(); // OK: 別のブロックスコープ
break;
}
}
パターン8: オブジェクトの分割代入と宣言
オブジェクトの分割代入の問題
// NG: 宣言なしの分割代入は括弧が必要
let a, b;
{ a, b } = obj;
// SyntaxError: Unexpected token '='
// { がブロック文として解釈される
// OK: () で囲む
let a2, b2;
({ a: a2, b: b2 } = obj);
// OK: 宣言と同時に行う場合は括弧不要
const { a: a3, b: b3 } = obj;
パターン9: 条件式での代入と比較の間違い
条件式での代入ミス
// NG: constで宣言した変数に再代入
const status = 'active';
status = 'inactive'; // TypeError: Assignment to constant variable.
// NG: if文で = を使用(SyntaxErrorではなく論理エラー)
if (x = 10) { // 代入してしまう(常にtrue)
// ...
}
// OK: === で比較
if (x === 10) {
// ...
}
パターン10: async/awaitの忘れ
async/await の忘れ
// NG: await を忘れてPromiseオブジェクトを直接操作
async function getUsers() {
const response = fetch('/api/users'); // await を忘れている
const data = await response.json(); // Promiseに.json()を呼んでしまう
// TypeError: response.json is not a function
}
// OK: await を付ける
async function getUsers() {
const response = await fetch('/api/users');
const data = await response.json();
return data;
}
まとめ:Unexpected token エラーの解決フローチャート
Unexpected token エラーに遭遇した際の体系的な解決手順をまとめます。以下のフローチャートに沿って問題を特定し、修正していきましょう。
解決フローチャート
- エラーメッセージを読む – 「Unexpected token X」のXが何か確認
- エラー行を確認 – ただし、原因はエラー行より前にあることが多い
- Unexpected token < の場合 – HTMLがJSとして読まれていないか確認(Network タブ)
- JSON.parse関連の場合 – パースする文字列の中身をconsole.logで確認
- 括弧の対応を確認 – エディタの括弧マッチング機能を使う
- カンマ・セミコロンを確認 – オブジェクトはカンマ、for文はセミコロン
- 文字列のクォートを確認 – 閉じ忘れ、全角文字の混入がないか
- ESLintを実行 – 静的解析で構文エラーを事前に検出
- Prettierでフォーマット – 自動整形がエラーになる箇所が原因
- 環境を確認 – Node.jsのバージョン、ブラウザの対応状況
エラーパターン別の原因と解決方法一覧
| エラーパターン |
主な原因 |
解決方法 |
Unexpected token '}' |
波括弧の閉じ忘れまたは余分 |
括弧のペアを確認 |
Unexpected token ')' |
丸括弧の対応ミス |
関数呼び出しや条件式の括弧を確認 |
Unexpected identifier |
カンマ忘れ、予約語の使用 |
プロパティ間のカンマ、変数名を確認 |
Unexpected string |
配列要素間のカンマ忘れ |
カンマの有無を確認 |
Unexpected token '<' |
HTMLがJSとして解析されている |
ファイルパス、サーバーレスポンスを確認 |
Unexpected end of input |
閉じ括弧・クォートが不足 |
ファイル全体の括弧対応を確認 |
Unexpected token ILLEGAL |
不可視文字、全角文字の混入 |
エディタで不可視文字を表示 |
Unexpected token ';' |
オブジェクト内でセミコロン使用 |
カンマに変更 |
Unexpected token ':' |
アロー関数でオブジェクト返却 |
() => ({ }) で囲む |
Unexpected token '.' |
Optional Chaining の未サポート |
環境を更新、またはBabelを使用 |
エラー予防のためのツールと設定
| ツール |
役割 |
おすすめ設定 |
| ESLint |
構文エラーの検出、コード品質チェック |
eslint --init で初期設定 |
| Prettier |
コードの自動整形 |
保存時に自動実行する設定 |
| VSCode |
リアルタイムのエラー表示 |
ESLint拡張機能をインストール |
| Babel |
新しい構文の下位互換変換 |
@babel/preset-env |
関連するJavaScriptエラー
SyntaxError以外にもJavaScriptにはよくあるエラーがあります。以下の記事も参考にしてください。
この記事のまとめ
- Unexpected token は構文解析時のエラーで、コードは1行も実行されない
- エラーが指す行ではなく、それより前の行に本当の原因がある場合が多い
- 括弧の対応(
{} () [])の確認が最優先
- JSON.parse のエラーはレスポンスの中身を先に確認する
- アロー関数でオブジェクトを返す場合は
=> ({ }) で括弧で囲む
Unexpected token < はHTMLがJSとして読み込まれている(URLミス・404・認証切れ)
- ESLint + Prettier で構文エラーの大半は事前に防げる
- 新しい構文(
?. ??)は古い環境ではBabelが必要