- 技術 💻
await is only validの原因と直し方|forEach・Jest・TS
結論
このエラーは、await を「async の付いていない関数の中」か「モジュールではないファイルの最上位」に書いたときに出ます。await のすぐ外側の関数(forEach などのコールバックも含む)に async を付けるか、最上位で使うなら Node.js は "type": "module" か .mjs、ブラウザは <script type="module"> にします。
この記事でわかること
- async の付け忘れ・forEach の中・CommonJS・ブラウザ・Jest・TypeScript 別の原因と直し方
- Node.js 22・20・21 と Chromium 153 で出した原文と、直したあとの出力(forEach と for...of・Promise.all の時間の実測つき)
- ESM では文言が「Unexpected reserved word」に変わる点と、どの直し方を選ぶかの判断表
この記事で実際に動かしたもの:Node.js 22.23.4・20.20.0・21.7.3 で package.json の "type"・拡張子・フラグを変えた20個以上の小さなファイル、Jest 30.5.2、TypeScript 6.0.3(tsc)、127.0.0.1 の自作サーバーで配信した9つのページを開いた Chromium 153。
直す場所は「await が書かれている場所」で決まります。まず、await のすぐ外側が関数か、ファイルの最上位かを見ます。
flowchart LR
W{await の<br>外側は?} -->|関数| A[関数に async]
W -->|コールバック| B[for...of か<br>Promise.all]
W -->|最上位| C[type=module<br>か .mjs]
やりたいことごとに、いちばん早い方法を先にまとめます。
| やりたいこと | いちばん早い方法 | 詳しい節 |
|---|---|---|
| 関数の中の await で出た | その関数の前に async を付ける | 原因1:async の付け忘れ |
| forEach の中の await で出た | 順番なら for...of、まとめてなら Promise.all と map | forEachの中のawaitを直す |
| setTimeout・addEventListener・then の中で出た | そのコールバックを async () => { } にする | 原因2:コールバックの中 |
| node app.js の最上位の await で出た | package.json に "type": "module" を足す | 原因3:CommonJSでトップレベルawait |
| package.json を変えられない | そのファイルを .mjs にするか、async の即時関数で包む | Node.jsでトップレベルawaitを使う4つの方法 |
| ブラウザのコンソールで出た | script タグに type="module" を付ける | ブラウザで出る原因と直し方 |
| Jest・tsc で出た | test のコールバックに async、tsc は module を nodenext に | Jest・TypeScriptで出る場合 |
| 直したら別のエラーが出た | 文言で見分ける(Unexpected reserved word など) | 直したあとに出る近いエラー |
確認環境:Ubuntu 24.04.5 LTS の上で、Node.js 22.23.4(npm 10.9.9)を主に使い、作業用のフォルダーに package.json の "type" の有無・値、拡張子(.js・.mjs・.cjs)、--no-experimental-detect-module などのフラグを変えた小さなファイルを作って、エラーの原文と直したあとの出力を記録しました。版による違いは、同じ環境に入っていた Node.js 20.20.0 と 21.7.3 で同じファイルを実行して比べています。テストは Jest 30.5.2(作業用のフォルダーに npm で導入)、TypeScript は typescript 6.0.3 の tsc で --noEmit を付けて型チェックだけを行いました。ブラウザは Python 3.13 の http.server を 127.0.0.1 に立ててページを配信し、Playwright 1.63.0 の Chromium 153.0.8010.12 で開いて、ページのエラー(pageerror)と Chrome DevTools Protocol の例外イベントを集めました。Firefox・Safari の文言は MDN、Node.js の構文の自動判定とトップレベル await の仕様は Node.js 公式ドキュメント、Jest の ESM 対応は Jest 公式ドキュメント(30.5)で確かめています。Windows・Mac は手元で動かしていないため、エラーの文言は同じ V8 エンジンのものとして扱い、OS で違う部分(環境変数の書き方)は公式ドキュメントの範囲だけを書きました。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年11月6日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「await is only validの原因と直し方|forEach・Jest・TS」
await is only valid in async functionsの意味(エラー原文)
この節では、エラー文がそれぞれの言葉で何を言っているのかと、実際にどう表示されるのかが分かります。
直訳すると「await は、async 関数の中と、モジュールの最上位でだけ使えます」です。await は「Promise の結果が出るまで、この関数の続きを止めて待つ」ための言葉です。止めてよいのは、止まっても呼び出し元が困らない async 関数の中と、ファイル全体の読み込みを待てる ES モジュール(ESM)の最上位だけです。それ以外の場所に書くと、プログラムを動かす前の構文チェックの段階で SyntaxError になります。
Node.js 22.23.4 で、async の付いていない関数の中に await を書いた app.js を実行すると、次のように出ました。
$ cat app.js
function getUser() {
const res = await fetch('http://127.0.0.1:9/');
return res.status;
}
getUser();
$ node app.js
/dev/shm/t308/lab/c1/app.js:2
const res = await fetch('http://127.0.0.1:9/');
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
at wrapSafe (node:internal/modules/cjs/loader:1713:18)
at Module._compile (node:internal/modules/cjs/loader:1755:20)
at Object..js (node:internal/modules/cjs/loader:1913:10)
at Module.load (node:internal/modules/cjs/loader:1505:32)
at Function._load (node:internal/modules/cjs/loader:1309:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:254:19)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49
Node.js v22.23.4
$ echo $?
1
読むところは3つです。
- 1行目の「ファイル名:行番号」と
^^^^^:どの await でつまずいたか。直すのは、この await を囲んでいる一番内側の関数です。 - スタックの
cjs/loaderとwrapSafe:ファイルを CommonJS として読み、構文を調べた段階で止まったという意味です。getUser() は一度も呼ばれていません。 - 終了コード 1:ファイルの1行目も実行されずに終わります。手元では、エラーの前に書いた console.log も表示されませんでした。
同じ原因でも、実行する場所で文言が違います。MDN は次の3つを挙げています。
| 実行する環境 | 表示される文言 |
|---|---|
| Chrome・Edge・Node.js(V8) | SyntaxError: await is only valid in async functions and the top level bodies of modules |
| Firefox | SyntaxError: await is only valid in async functions, async generators and modules |
| Safari | SyntaxError: Unexpected identifier |
検索では短く「await is only valid in async function」と打たれることも多いですが、指している問題は同じです。Safari の「Unexpected identifier」は、モジュールでないスクリプトでは await がただの変数名として読まれ、その次の語(fetch など)が予想外だという意味です。手元の Chromium でも、(await fetch('/data.json')).status のように括弧で囲んだ書き方では、V8 の文言ではなく SyntaxError: Unexpected identifier 'fetch' が出ました。
原因の一覧表と、最初に確かめる3つのこと
この節では、このエラーの原因が何通りあるかと、どれに当たるかをすぐに確かめる方法が分かります。
原因は「await を書いた場所」と「その場所が待てるかどうか」の食い違いです。手元で再現できた原因をまとめました。
| 番号 | await を書いた場所 | 手元で出た文言 | 直し方 |
|---|---|---|---|
| 1 | async の無い普通の関数・アロー関数 | await is only valid in async functions… | その関数に async |
| 2 | forEach・map・setTimeout・addEventListener・then のコールバック | 同上(外側の関数が async でも出る) | コールバックに async、または for...of |
| 3 | async 関数の中に入れ子にした普通の関数 | 同上 | 入れ子の関数に async |
| 4 | ジェネレーター関数・クラスの constructor | 同上 | async function* にする・初期化用の async メソッドに分ける |
| 5 | CommonJS("type": "commonjs"・.cjs)の最上位 | 同上+Failed to load the ES module の警告 | "type": "module"・.mjs・async の即時関数 |
| 6 | "type" 無しの .js の最上位(Node.js 21 など自動判定が無い版) | 同上 | "type" を明示するか版を上げる |
| 7 | ブラウザの普通の script(インライン・外部ファイル・onclick 属性) | 同上(onclick はクリックしたときに出る) | type="module"・async 関数で包む |
| 8 | new Function() の本文 | 同上 | AsyncFunction を使う |
| 9 | Jest のテストファイル(Babel で変換) | Unexpected reserved word 'await' | test のコールバックに async |
| 10 | TypeScript(tsc) | error TS1308・TS1309・TS1375・TS1378 | async・module の設定・export {} |
どれに当たるかは、次の3つで絞れます。
^^^^^ の外側の関数を指でたどる
エラーの ^^^^^ が指す await から上へ、最初に出てくる function か => を探します。そこに async が無ければ原因1〜4です。forEach( や setTimeout( の直後のアロー関数で止まったら原因2です。関数が1つも無く、ファイルの最上位に出たら原因5〜7です。
Node.js の版と "type" を見る
最上位の await なら、node -v と npm pkg get type を実行します。手元では "module"・"commonjs" のどちらか、書いていなければ {} と表示されました。v22.7.0 以降・v20.19.0 以降で "type" が無いときは、Node.js が最上位の await を見つけて ESM として読み直します。
そのファイルが ESM か CommonJS かを見る
同じフォルダーに console.log(typeof module === 'undefined' ? 'ESM' : 'CommonJS'); だけを書いた which.js を置いて node which.js を実行します。ESM と出たのにこのエラーが出るなら、最上位ではなく関数の中の問題(原因1〜4)です。
原因1:関数にasyncを付け忘れている(Node.js・ブラウザ共通)
この節では、いちばん多い「async の付け忘れ」の直し方と、直したあとに呼び出し側で気をつけることが分かります。
await は、すぐ外側の関数に async が付いていないと書けません。関数の書き方ごとに、async を付ける位置は次のとおりです。
// 関数宣言
async function getUser() { const res = await fetch(url); }
// 関数式・アロー関数
const getUser = async function () { await fetch(url); };
const getUser2 = async () => { await fetch(url); };
// オブジェクト・クラスのメソッド
const api = { async getUser() { await fetch(url); } };
class Api { async getUser() { await fetch(url); } }
async を付けると構文エラーは消えますが、関数の戻り値が Promise に変わる点に注意します。手元で、async を付けた getValue() をそのまま console.log すると次のようになりました。
async function getValue() {
await wait(50);
return 42;
}
const v = getValue();
console.log(v);
getValue().then((x) => console.log('then で受け取る:', x));
$ node noasync_return.js
Promise { <pending> }
then で受け取る: 42
呼び出し側で値を使うなら、呼び出し側も async にして await getValue() と書くか、.then() で受け取ります。こうして async が呼び出し元へ順に広がっていくのは正しい直し方の結果です。どこかで止めたいときは、いちばん外側(起動する処理)だけを .then().catch() か、後で説明する async の即時関数で受け止めます。
async 関数の中で失敗した Promise を受け止めないと、今度は「Uncaught (in promise)」が出ます。受け止め方は、関連記事「Uncaught (in promise)の原因と直し方」で扱っています。
入れ子の関数も同じ規則です。外側が async でも、内側に普通の関数を書くとその中では await を使えません。手元では、async 関数 main の中の const handler = function () { await ... } で同じ文言が出ました。クラスの constructor と、function* のジェネレーターでも出ます。constructor は async にできないため、static async create() のような初期化用のメソッドを作って、その中で await してからインスタンスを返します。ジェネレーターは async function* にすると、中で await でき、呼ぶ側は for await...of で受け取ります。
原因2:forEach・map・setTimeoutなどのコールバックの中でawait
この節では、外側の関数に async を付けているのにエラーが出る理由と、コールバックの種類ごとの直し方が分かります。
await の「すぐ外側」は、forEach に渡したアロー関数です。外側の main が async でも、コールバックに async が無ければエラーになります。手元のファイルと出力です。
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
async function main() {
[1, 2, 3].forEach((n) => {
await wait(100);
console.log(n);
});
}
main();
$ node app.js
/dev/shm/t308/lab/c2/app.js:4
await wait(100);
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
setTimeout・addEventListener・Promise の then に渡すコールバックでも同じでした。直し方は、そのコールバックに async を付けることです。
// setTimeout
setTimeout(async () => {
await wait(100);
console.log('done');
}, 0);
// ブラウザのイベント
button.addEventListener('click', async () => {
const res = await fetch('/data.json');
console.log(res.status);
});
ただし、forEach だけは async を付けても「待ってくれない」ので、次の節の書き方に変えます。forEach はコールバックの戻り値(Promise)を捨てるためです。
forEach の中で await したいとき
forEach は Promise を待たずに次の行へ進む
forEachの中のawaitを直す(for...of・Promise.allの実測)
この節では、forEach の中の await を直す3つの書き方で、実行順と時間がどう違うかが実測で分かります。
300ms・100ms・200ms かかる処理を3件、同じ条件で動かしました。書き方は次の3つです。
// A:forEach に async(エラーは消えるが待たない)
[300, 100, 200].forEach(async (ms) => {
await wait(ms);
console.log(`forEach: ${ms}ms 終了`);
});
console.log('forEach の次の行');
// B:for...of(1件ずつ順番に待つ)
for (const ms of [300, 100, 200]) {
await wait(ms);
console.log(`for...of: ${ms}ms 終了`);
}
// C:Promise.all と map(同時に始めて全部を待つ)
const results = await Promise.all(
[300, 100, 200].map(async (ms) => {
await wait(ms);
return `${ms}ms`;
})
);
Node.js 22.23.4 での出力です(括弧の中は開始からの経過時間)。
$ node foreach_async.js
forEach の次の行 (+1ms)
forEach: 100ms 終了 (+101ms)
forEach: 200ms 終了 (+202ms)
forEach: 300ms 終了 (+301ms)
$ node forof.js
for...of: 300ms 終了 (+301ms)
for...of: 100ms 終了 (+403ms)
for...of: 200ms 終了 (+604ms)
for...of の次の行 (+604ms)
$ node promiseall.js
[ '300ms', '100ms', '200ms' ] (+300ms)
読み取れることは3つです。
- A は終わる順番がばらばらで、次の行が先に動く:「全部終わってから保存する」のような処理を forEach の後ろに書くと、まだ終わっていない状態で動きます。forEach の中で失敗した Promise は誰も受け止めないため、Uncaught (in promise) の原因にもなります。
- B は確実に順番どおり:前の結果を次で使う、API の回数制限がある、といった場面に向きます。時間は合計になります。
- C はいちばん速く、結果の並びも元の配列の順:互いに関係のない処理に向きます。1件でも失敗すると全体が失敗するので、失敗しても残りの結果が欲しいときは
Promise.allSettledを使います。
map に async を付けて Promise.all を忘れると、エラーは出ずに Promise の配列が返ります。手元の出力は [ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ] でした。画面に「[object Promise]」や「Promise { <pending> }」が出たら、await か Promise.all が抜けています。
ループの種類ごとの違い(break できるか、インデックスの扱い)は関連記事「JavaScriptループ7種|forEachのbreak・for of/in比較」、async/await の並列実行とエラー処理の書き方は関連記事「JavaScriptのasync/awaitの使い方|エラー処理と並列実行」で扱っています。
原因3:CommonJSでトップレベルawait(Node.jsの"type"と版)
この節では、関数の外(ファイルの最上位)に書いた await が、Node.js の設定と版によって動いたり止まったりする理由が分かります。
トップレベル await(関数の外の await)は、ES モジュールでだけ使えます。Node.js は .js ファイルを、いちばん近い package.json の "type" で ESM か CommonJS かに分けます。.mjs は必ず ESM、.cjs は必ず CommonJS です。同じ3行のファイルを、置き場所と版を変えて実行しました。
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
await wait(100);
console.log('top-level await OK');
| 置き方 | Node.js 22.23.4 | Node.js 20.20.0 | Node.js 21.7.3 |
|---|---|---|---|
| "type": "commonjs" の .js | エラー(終了コード1) | エラー | エラー |
| .cjs | エラー | エラー | エラー |
| "type" 無しの .js | 動く(警告つき) | 動く(警告つき) | エラー |
| "type" 無し+--no-experimental-detect-module | エラー | — | — |
| "type": "module" の .js・.mjs | 動く | 動く | 動く |
"type": "commonjs" の .js では、Node.js 22 はエラーの前に直し方を表示しました。
$ node app.js
(node:2014) Warning: Failed to load the ES module: /dev/shm/t308/lab/c4/app.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/dev/shm/t308/lab/c4/app.js:2
await wait(100);
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
Node.js 20.20.0 の警告は Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. で、Node.js 21.7.3 は警告なしでエラーだけでした。.cjs のファイルでも Node.js 22 は同じ「Failed to load the ES module」の警告を出しましたが、.cjs は必ず CommonJS なので、警告のとおり "type" を変えても直りません。拡張子を .mjs にするか、async の即時関数で包みます。
"type" 無しの .js が Node.js 22・20 で動いたのは、構文の自動判定のためです。Node.js 公式ドキュメントは、CommonJS として評価するとエラーになる構文(import 文・export 文・import.meta・最上位の await など)を見つけたら ESM として読み直すと説明しています。既定で有効になったのは v22.7.0・v20.19.0 なので、v21 系では動きません。動いたときも次の警告が出ます。
$ node app.js
(node:2024) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///dev/shm/t308/lab/c5/app.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to /dev/shm/t308/lab/c5/package.json.
(Use `node --trace-warnings ...` to show where the warning was created)
top-level await OK
手元では動くのに CI や同僚の PC では止まる、というときはこの自動判定を疑います。Node.js の版が v21 系や v20.19.0 より前だと、"type" の無い .js は必ず CommonJS として読まれます。"type" を明示すると、版によらず同じ結果になります。ESM と CommonJS の読み分けの全体は、関連記事「Node.js ESMとCommonJSの違い|import混在の解決表」で扱っています。
Node.jsでトップレベルawaitを使う4つの方法(type・.mjs・即時関数・import())
この節では、最上位で await したいときの直し方を4つ比べ、どれを選ぶと周りに影響が少ないかが分かります。
"type": "module"
package.json に1行足す。フォルダーの下の .js がすべて ESM になる。
新しいプロジェクト向け
拡張子を .mjs
そのファイルだけ ESM にする。ほかのファイルは CommonJS のまま。
一部だけ変えたい
async の即時関数
最上位の処理を (async () => { … })() で包む。CommonJS のまま動く。
設定を変えられない
import() で読む
CommonJS から、トップレベル await を含む ESM を動的に読む。
require で読めないとき
1と2の結果です。
$ cat package.json
{"type":"module"}
$ node app.js
type: module ならトップレベル await が動く
$ node app.mjs
.mjs ならトップレベル await が動く
3の async の即時関数(IIFE)は、CommonJS のまま、どの版でも動きます。最後に .catch() を付けて、失敗したときに終了コードを1にしておくと、CI で失敗に気づけます。
const { wait } = require('./wait.js');
(async () => {
const v = await wait(100, 'IIFE の中で await できた');
console.log(v);
})().catch((err) => {
console.error(err);
process.exitCode = 1;
});
$ node iife.js
IIFE の中で await できた
4の import() は、トップレベル await を含む ESM を CommonJS から使うときの方法です。Node.js 22.12 以降は require() でも ESM を読めますが、読む先にトップレベル await があると require() は失敗します。手元の出力です。
$ cat ../m/lib_tla.js
export const config = await Promise.resolve({ port: 3000 });
$ node use_lib.cjs
node:internal/modules/esm/module_job:450
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
^
Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
From /dev/shm/t308/lab/c4/use_lib.cjs
Requiring /dev/shm/t308/lab/m/lib_tla.js
code: 'ERR_REQUIRE_ASYNC_MODULE'
}
$ node use_lib_import.cjs
import() で読めた: { port: 3000 }
どのファイルのトップレベル await が原因かは、エラー文のとおり --experimental-print-required-tla を付けると表示されました(Error: unexpected top-level await at file:///dev/shm/t308/lab/m/lib_tla.js:1)。読む先のパッケージが深い場所にあるときに便利です。
コマンドラインで1行だけ試すときは、node -e 'await …' も Node.js 22 では自動判定で動きました。--input-type=module を付けると判定に頼らず ESM になります。対話モード(REPL)では、最初から最上位で await できました。
$ node --input-type=module -e 'const v = await Promise.resolve("input-type=module OK"); console.log(v)'
input-type=module OK
$ node -i
> await Promise.resolve("REPL ではトップレベル await が使える")
'REPL ではトップレベル await が使える'
ブラウザで出る原因と直し方(script type="module")
この節では、ブラウザでこのエラーが出る3つの書き方と、type="module" で直したときの結果が分かります。
ブラウザの普通の <script> はモジュールではないので、最上位で await を使えません。127.0.0.1 で配信したページを Chromium 153 で開き、出たエラーを集めました。
| ページの書き方 | Chromium 153 で出たもの |
|---|---|
| <script> の最上位で await | SyntaxError: await is only valid in async functions and the top level bodies of modules |
| <script src="app.js">(中身が最上位の await) | 同上 |
| <script> の普通の関数の中で await | 同上 |
| onclick 属性に await | 同上(読み込み時は出ず、クリックしたときに出た) |
| <script type="module"> の普通の関数の中で await | SyntaxError: Unexpected reserved word |
| <script type="module"> の最上位で await | 動いた(type="module" で読めた: {"name":"rjc"}) |
| async の即時関数・async のリスナー | 動いた(async IIFE で読めた: rjc/async を付けたリスナー: 200) |
直し方は、script タグに type="module" を付けることです。
<!-- エラーになる -->
<script>
const res = await fetch('/data.json');
</script>
<!-- 動く -->
<script type="module">
const res = await fetch('/data.json');
const data = await res.json();
console.log('type="module" で読めた:', JSON.stringify(data));
</script>
type="module" にすると、最上位で宣言した変数がほかの普通の script から見えなくなる、defer と同じく HTML を読み終えてから動く、file:// で開くと読み込めない、といった変化があります。既存のコードがグローバル変数に頼っているなら、async の即時関数で包むほうが影響は小さく済みます。
エラーの出たスクリプトは1行も動きません。手元で、1つ目の script の1行目に console.log を置き、4行目に await を書いたページでは、1行目のログは出ず、別の2つ目の script だけが動きました。
[exception] SyntaxError: await is only valid in async functions and the top level bodies of modules @ partial.html:4:15
[console] 2つ目の script は動く
「ボタンを押しても何も起きない」ときは、onclick 属性の中の await も疑います。onclick 属性はクリックされたときに初めて関数として組み立てられるため、ページを開いた時点ではコンソールに何も出ませんでした。HTML の属性ではなく、async を付けた addEventListener に書き換えると動きました。
自分で確かめる:DevTools のコンソール(F12 →「Console」)を開き、赤い行の右端に出る「ファイル名:行番号」を押すと、エラーになった await の位置が Sources パネルで開きます。なお、DevTools のコンソールに直接打つ await fetch('/') は、エラーにならず動きます。手元で DevTools Protocol の Runtime.evaluate に replMode を付けて評価すると 200 が返り、付けないと SyntaxError: Unexpected identifier 'fetch' になりました。コンソールで動いたコードを script に貼ると止まるのはこのためです。
Jest・TypeScriptで出る場合(文言が変わる)
この節では、Jest と TypeScript では同じ原因でも文言が変わることと、それぞれの直し方が分かります。
Jest は既定でテストファイルを Babel で変換するため、関数の中の await は Babel の文言になります。Jest 30.5.2 で、async の無い test のコールバックに await を書いたときの出力です。
const { fetchUser } = require('./api.js');
test('ユーザーを取得できる', () => {
const user = await fetchUser(1);
expect(user.name).toBe('taro');
});
$ npx jest a.test.js
FAIL ./a.test.js
● Test suite failed to run
Jest encountered an unexpected token
...
Details:
SyntaxError: /dev/shm/t308/jest/a.test.js: Unexpected reserved word 'await'. (3:15)
1 | const { fetchUser } = require('./api.js');
2 | test('ユーザーを取得できる', () => {
> 3 | const user = await fetchUser(1);
| ^
4 | expect(user.name).toBe('taro');
5 | });
「Jest encountered an unexpected token」と「transformIgnorePatterns」などの案内が先に出るので設定の問題に見えますが、Details の下の1行が本当の原因です。test のコールバックに async を付けると通りました。forEach の中の await も同じ文言(5:14)になり、for...of に変えると通りました。
テストファイルの最上位に await を書いた場合は、Babel を通ったあと Node.js 側で V8 の文言が出ました。
Details:
/dev/shm/t308/jest/b.test.js:4
const user = await fetchUser(1);
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
at ModuleExecutor.compile (node_modules/jest-runtime/build/index.js:3081:44)
直し方は2通りです。CommonJS のままなら、準備の処理を beforeAll(async () => { user = await fetchUser(1); }) に移します。ESM のまま最上位で await したいなら、Jest 公式ドキュメント(30.5)のとおり transform: {} で変換を止め、--experimental-vm-modules を付けて Node.js を起動します。手元では、.mjs のテストを付けずに動かすと Must use import to load ES Module で止まり、付けると通りました。
$ npx jest --config '{}' a.test.js b.test.js c.test.js
Tests: 3 passed, 3 total
$ NODE_OPTIONS=--experimental-vm-modules npx jest d.test.mjs
(node:6451) ExperimentalWarning: VM Modules is an experimental feature and might change at any time
Tests: 1 passed, 1 total
TypeScript は、実行する前に tsc がエラー番号つきで教えてくれます。typescript 6.0.3 で出た4つの番号です。
| 番号 | tsc の文言(原文) | 出た条件と直し方 |
|---|---|---|
| TS1308 | 'await' expressions are only allowed within async functions and at the top levels of modules. | 関数に async が無い。async を付ける |
| TS1309 | The current file is a CommonJS module and cannot use 'await' at the top level. | module: nodenext で "type" が無い・commonjs。"type": "module" にする |
| TS1375 | 'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. | import も export も無いファイル。export {} を足す |
| TS1378 | Top-level 'await' expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', 'node18', 'node20', 'nodenext', or 'preserve', and the 'target' option is set to 'es2017' or higher. | module: commonjs、または target: es2016 以下。module と target を上げる |
Node.js で動かすなら、tsconfig の module を nodenext にし、package.json に "type": "module" を書くと、手元ではトップレベル await を含むファイルの tsc がエラー無しで終わりました。async 関数の戻り値の型(Promise<T>・Awaited)は、関連記事「TypeScript async/awaitの型|Promise・Awaited」で扱っています。
直したあとに出る近いエラーの見分け方
この節では、await まわりを直したあとに出る別の文言が、それぞれ何を意味するかが分かります。
"type": "module" や .mjs で ESM にしたあとは、関数の中の await の文言が変わります。ESM では await が予約語になるため、async の無い関数の中では「await is only valid…」ではなく次の文言になりました。Node.js 20.20.0 でも同じでした。
$ node infunc.js
file:///dev/shm/t308/lab/m/infunc.js:2
return await Promise.resolve(1);
^^^^^
SyntaxError: Unexpected reserved word
CommonJS・普通の script
await is only valid in async functions and the top level bodies of modules
場所は /path/app.js:2 の形
await を変数名に使えてしまう(const await = 1 が動いた)
ES モジュール(type: module・.mjs)
SyntaxError: Unexpected reserved word
場所は file:///path/app.js:2 の形
await は予約語(const await = 1 もこの文言)
どちらも原因は同じで、関数に async を付ければ直ります。「Unexpected reserved word」は、ジェネレーター関数・クラスの constructor・入れ子の普通の関数の中でも同じように出ました。
ほかに手元で出た、近いけれど別のエラーです。
- ReferenceError: Cannot determine intended module format because both require() and top-level await are present.(code: 'ERR_AMBIGUOUS_MODULE_SYNTAX'):"type" の無い .js に require() とトップレベル await を両方書いたときに Node.js 22 で出ました。CommonJS のままにするなら await を async 関数で包み、ESM にするなら require を import に直します。
- Warning: Detected unsettled top-level await:最上位で await した Promise がいつまでも終わらないときに出て、終了コードは13でした。Node.js 公式ドキュメントにも、終わらないトップレベル await では終了コード13で終わると書かれています。resolve を呼び忘れていないか、イベントを待ったまま止まっていないかを見ます。
- Error [ERR_EVAL_ESM_CANNOT_PRINT]: --print cannot be used with ESM input:
node -p 'await …'のように -p(結果を表示)と最上位の await を組み合わせると出ました。node --input-type=module -eと console.log に分けます。 - Promise { <pending> }:エラーではなく、async 関数の戻り値をそのまま表示したときの出力です。呼び出し側で await か then が抜けています。
import 文の行で止まる「Cannot use import statement outside a module」は、同じく ESM と CommonJS の食い違いが原因の別のエラーで、別記事「Cannot use import statementの原因と直し方|Jest・TS」で扱います。
文字列から関数を作る new Function("return await …") でも「await is only valid…」が出ました。(async () => {}).constructor で取り出した AsyncFunction を使うと、手元では「AsyncFunction なら動く」と表示されました。
OS・Node.jsの版・実行環境別の違い
この節では、Windows・Mac・Linux や Node.js の版で、このエラーの出方と直し方が変わるかが分かります。
エラーの文言と原因は OS では変わりません。文言を出しているのは JavaScript エンジン(Chrome・Edge・Node.js は V8)で、Windows・Mac・Linux のどれでも同じ構文の規則で判断されます。OS で変わるのは、設定のしかたのほうです。
| 環境 | 違いが出るところ | 書き方 |
|---|---|---|
| Linux・Mac(bash・zsh) | Jest の ESM モードの環境変数 | NODE_OPTIONS=--experimental-vm-modules npx jest |
| Windows(PowerShell) | 同上 | $Env:NODE_OPTIONS = "--experimental-vm-modules" の次の行で npx jest |
| Windows(コマンドプロンプト) | 同上 | set NODE_OPTIONS=--experimental-vm-modules の次の行で npx jest |
| OS 共通 | package.json に書く方法 | "scripts" に node --experimental-vm-modules node_modules/jest/bin/jest.js |
PowerShell の $Env: とコマンドプロンプトの set の書き方は Microsoft Learn(about_Environment_Variables・set)で確かめた範囲で、どちらもその画面を閉じると元に戻ります。環境変数の書き方が OS で違うのを避けたいときは、Jest 公式ドキュメントにある node --experimental-vm-modules node_modules/jest/bin/jest.js を package.json の scripts に書くと、どの OS でも npm test で同じように動きます。
違いが大きいのは Node.js の版です。トップレベル await まわりの変化を、Node.js 公式ドキュメントの版の履歴で並べました。
- 1v14.8.0:ESM のトップレベル await がフラグ無しで使えるようになったCommonJS では今も使えない
- 2v20.10.0・v21.1.0:構文の自動判定がフラグ付きで追加この時点では既定で無効。手元の 21.7.3 も "type" 無しでエラー
- 3v22.7.0・v20.19.0:構文の自動判定が既定で有効"type" 無しの .js でもトップレベル await が警告つきで動く
- 4v22.12.0・v20.19.0:require() で ESM を読めるただし読む先にトップレベル await があると ERR_REQUIRE_ASYNC_MODULE
同じコードが CI だけで止まるときは、CI の設定で指定している Node.js の版と、手元の node -v を並べて見ます。"type" を明示しておけば、自動判定の有無に左右されません。
よくあるエラー/つまずき一覧(原文つき)
この節では、このエラーまわりで手元で出た原文と、その原因・直し方を1つの表で引けます。
| 症状・原文 | 原因 | 直し方 |
|---|---|---|
| SyntaxError: await is only valid in async functions and the top level bodies of modules(関数の中) | すぐ外側の関数に async が無い | その関数に async を付ける |
| 同上(forEach・setTimeout の中) | コールバックに async が無い | for...of・Promise.all か、コールバックに async |
| 同上+Warning: Failed to load the ES module | CommonJS の最上位で await | "type": "module"・.mjs・async の即時関数 |
| SyntaxError: Unexpected reserved word | ESM の中で、async の無い関数に await | その関数に async を付ける |
| SyntaxError: Unexpected reserved word 'await'.(Jest) | Babel が変換したテストで async の付け忘れ | test のコールバックに async |
| SyntaxError: Unexpected identifier 'fetch' | 普通の script で (await fetch(…)) と括弧で書いた | type="module" か async 関数で包む |
| ERR_AMBIGUOUS_MODULE_SYNTAX | require() とトップレベル await が同じファイルにある | どちらかにそろえる |
| ERR_REQUIRE_ASYNC_MODULE | トップレベル await を含む ESM を require() した | import() で読む |
| Warning: Detected unsettled top-level await(終了コード13) | 最上位で待った Promise が終わらない | resolve の呼び忘れを直す |
| Promise { <pending> } と表示される | async 関数の戻り値を待たずに使った | 呼び出し側で await か then |
| ボタンを押すと初めてエラー | onclick 属性の中に await | async を付けた addEventListener に移す |
直す前に確かめると早い4点です。
- 1^^^^^ の外側の関数を見るforEach などのコールバックは「別の関数」として数える
- 2最上位なら node -v と npm pkg get type を控える版と "type" で、Node.js の結果はほぼ決まる
- 3async を付けたら呼び出し側も見る戻り値が Promise に変わるので、await か then が要る
- 4forEach に async を付けて終わりにしないエラーは消えても、手元では次の行が1msで先に動いた
判断表:どの直し方を選ぶか
この節では、自分の状況に合う直し方を1つ選べます。
迷ったら「await のすぐ外側が関数かどうか」で分けます。関数なら async を付ける(ループなら書き方を変える)、最上位なら ESM にするか即時関数で包みます。
| 状況 | 選ぶ直し方 | 理由 |
|---|---|---|
| 普通の関数の中 | 関数に async、呼び出し側も await | いちばん小さな変更で済む |
| ループで1件ずつ、順番が大事 | for...of | 手元で順番どおり、合計604ms |
| ループで互いに関係なく、速さが大事 | Promise.all と map | 手元で300ms。失敗も拾うなら allSettled |
| 新しい Node.js のプロジェクトで最上位 | "type": "module" | 版によらず ESM になり、警告も出ない |
| 既存の CommonJS のプロジェクトで最上位 | async の即時関数か、そのファイルだけ .mjs | ほかのファイルの require・__dirname を壊さない |
| ブラウザで最上位 | script に type="module" | グローバル変数に頼るなら即時関数で包む |
| Jest のテスト | test に async、準備は beforeAll | ESM モードは実験的機能のため |
| TypeScript | module: nodenext と "type": "module" | TS1309・TS1378 がまとめて消える |
CommonJS のプロジェクトを "type": "module" に変えると、ほかのファイルの require や __dirname で別のエラーが出ることがあります。変える前に grep -rn "require(\|__dirname" --include=*.js . で使っている場所を探しておくと、影響の大きさが分かります。
よくある質問
この節では、このエラーについてよく聞かれることに短く答えます。
Q. 外側の関数に async を付けているのにエラーが出ます。 A. await のすぐ外側に別の関数が無いかを見ます。forEach・map・setTimeout・addEventListener・then に渡したアロー関数は、それぞれ別の関数です。手元でも、async の main の中の forEach で同じ文言が出ました。
Q. forEach に async を付けたらエラーは消えましたが、結果がおかしいです。 A. forEach は Promise を待たないので、次の行が先に動きます。手元では次の行が1ms後に動き、3件の終わる順番もばらばらでした。順番なら for...of、まとめてなら Promise.all に書き換えます。
Q. 関数の外で await を使う方法はありますか?
A. ES モジュールなら使えます(トップレベル await)。Node.js は "type": "module" か .mjs、ブラウザは type="module" の script です。CommonJS のままなら、async の即時関数 (async () => { … })() で包みます。
Q. 手元では動くのに、CI や別の PC ではこのエラーになります。 A. Node.js の版の違いを疑います。"type" の無い .js のトップレベル await は、手元の Node.js 22.23.4 と 20.20.0 では自動判定で動き、21.7.3 ではエラーでした。package.json に "type" を明示すると同じ結果になります。
Q. "type": "module" にしたら「Unexpected reserved word」に変わりました。 A. ESM では await が予約語になり、async の無い関数の中の await の文言がこれに変わります。原因は同じなので、その関数に async を付けます。
Q. DevTools のコンソールでは動いたのに、script に書くと止まります。 A. DevTools のコンソールは、最上位の await を特別に受け付けます。ページの普通の script は受け付けないので、type="module" にするか async 関数で包みます。
Q. async を付けると、その関数を呼ぶ側も全部 async にしないといけませんか?
A. 戻り値を使う側は await か then で受け取る必要があります。起動する処理など、どこか1か所で .then().catch() を使って受け止めれば、そこから上は async にしなくても動きます。
Q. Jest で「Jest encountered an unexpected token」と出ました。設定を変えるべきですか? A. 先に Details の下を読みます。「Unexpected reserved word 'await'」なら設定ではなく、test のコールバックの async の付け忘れです。手元では async を付けるだけで通りました。
まとめ
この節では、記事の要点を5つにまとめます。
- 原因は、await を「async の無い関数」か「モジュールでない最上位」に書いたこと。^^^^^ の外側の関数を見ると場所が決まります。
- forEach などのコールバックも「別の関数」。forEach は async を付けても待たないので、for...of(手元で604ms・順番どおり)か Promise.all(300ms)に書き換えます。
- Node.js の最上位は "type": "module"・.mjs・async の即時関数。"type" 無しの .js は Node.js 22.7・20.19 以降だけ自動判定で動くので、"type" を明示します。
- ブラウザは script に type="module"。エラーの出た script は1行も動かず、onclick 属性はクリックしたときに初めてエラーが出ます。
- ESM・Jest・TypeScript では文言が変わる。Unexpected reserved word、Unexpected reserved word 'await'、TS1308〜TS1378 も、同じ考え方で直せます。
参考・出典
確認日はいずれも 2026年11月6日です。
- MDN「SyntaxError: await is only valid in async functions, async generators and modules」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Bad_await
- MDN「await」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
- MDN「async function」:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- MDN「JavaScript modules」Top level await:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
- Node.js「Modules: ECMAScript modules」Top-level await:https://nodejs.org/api/esm.html
- Node.js「Modules: Packages」Determining module system・Syntax detection:https://nodejs.org/api/packages.html
- Node.js「Modules: CommonJS modules」Loading ECMAScript modules using require():https://nodejs.org/api/modules.html
- Node.js「Process」Exit codes:https://nodejs.org/api/process.html
- Jest「ECMAScript Modules」(30.5):https://jestjs.io/docs/ecmascript-modules
- TypeScript「Modules - Reference」:https://www.typescriptlang.org/docs/handbook/modules/reference.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「await is only validの原因と直し方|forEach・Jest・TS」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。